1use crate::writer::{Result, XmlWriter};
2use std::io::Write;
3use xlsbye_core::types::{
4 Alignment, Border, BorderSide, BorderStyle, Color, ColorType, DxfStyle, Fill, Font,
5 FontScheme, GradientFill, GradientType, HorizontalAlign, NumFmt, PatternFill, PatternType,
6 Stylesheet, SuperSub, UnderlineStyle, VerticalAlign, Xf,
7};
8use xlsbye_core::xml_names::SPREADSHEET_NS;
9
10pub fn write_styles(writer: impl Write, stylesheet: &Stylesheet) -> Result<()> {
11 let mut writer = XmlWriter::new(writer);
12 writer.write_xml_declaration()?;
13 writer.write_start_element_with_ns(
14 "styleSheet",
15 [("", SPREADSHEET_NS)],
16 std::iter::empty::<(&str, &str)>(),
17 )?;
18
19 let custom_num_fmts: Vec<&NumFmt> = stylesheet
20 .num_fmts
21 .iter()
22 .filter(|fmt| fmt.id > 163)
23 .collect();
24 writer.write_start_element(
25 "numFmts",
26 [("count", custom_num_fmts.len().to_string())],
27 )?;
28 for num_fmt in custom_num_fmts {
29 writer.write_empty_element(
30 "numFmt",
31 [
32 ("numFmtId".to_string(), num_fmt.id.to_string()),
33 ("formatCode".to_string(), num_fmt.format_code.clone()),
34 ],
35 )?;
36 }
37 writer.write_end_element("numFmts")?;
38
39 writer.write_start_element("fonts", [("count", stylesheet.fonts.len().to_string())])?;
40 for font in &stylesheet.fonts {
41 write_font(&mut writer, font)?;
42 }
43 writer.write_end_element("fonts")?;
44
45 writer.write_start_element("fills", [("count", stylesheet.fills.len().to_string())])?;
46 for fill in &stylesheet.fills {
47 write_fill(&mut writer, fill)?;
48 }
49 writer.write_end_element("fills")?;
50
51 writer.write_start_element(
52 "borders",
53 [("count", stylesheet.borders.len().to_string())],
54 )?;
55 for border in &stylesheet.borders {
56 write_border(&mut writer, border)?;
57 }
58 writer.write_end_element("borders")?;
59
60 writer.write_start_element(
61 "cellStyleXfs",
62 [("count", stylesheet.cell_style_xfs.len().to_string())],
63 )?;
64 for xf in &stylesheet.cell_style_xfs {
65 write_xf(&mut writer, xf)?;
66 }
67 writer.write_end_element("cellStyleXfs")?;
68
69 writer.write_start_element(
70 "cellXfs",
71 [("count", stylesheet.cell_xfs.len().to_string())],
72 )?;
73 for xf in &stylesheet.cell_xfs {
74 write_xf(&mut writer, xf)?;
75 }
76 writer.write_end_element("cellXfs")?;
77
78 write_cell_styles(&mut writer, stylesheet.cell_style_xfs.len())?;
79
80 writer.write_start_element("dxfs", [("count", stylesheet.dxfs.len().to_string())])?;
81 for dxf in &stylesheet.dxfs {
82 write_dxf(&mut writer, dxf)?;
83 }
84 writer.write_end_element("dxfs")?;
85
86 writer.write_empty_element(
87 "tableStyles",
88 [
89 ("count", "0"),
90 ("defaultTableStyle", "TableStyleMedium9"),
91 ("defaultPivotStyle", "PivotStyleLight16"),
92 ],
93 )?;
94
95 writer.write_end_element("styleSheet")?;
96 Ok(())
97}
98
99fn write_cell_styles<W: Write>(writer: &mut XmlWriter<W>, cell_style_xf_count: usize) -> Result<()> {
100 let count = cell_style_xf_count.max(1);
101 writer.write_start_element("cellStyles", [("count", count.to_string())])?;
102
103 for index in 0..count {
104 let mut attrs = vec![
105 (
106 "name".to_string(),
107 if index == 0 {
108 "Normal".to_string()
109 } else {
110 format!("Style {}", index)
111 },
112 ),
113 ("xfId".to_string(), index.to_string()),
114 ];
115 if index == 0 {
116 attrs.push(("builtinId".to_string(), "0".to_string()));
117 }
118 writer.write_empty_element("cellStyle", attrs)?;
119 }
120
121 writer.write_end_element("cellStyles")
122}
123
124fn write_font<W: Write>(writer: &mut XmlWriter<W>, font: &Font) -> Result<()> {
125 writer.write_start_element("font", std::iter::empty::<(&str, &str)>())?;
126
127 if font.bold {
128 writer.write_empty_element("b", std::iter::empty::<(&str, &str)>())?;
129 }
130 if font.italic {
131 writer.write_empty_element("i", std::iter::empty::<(&str, &str)>())?;
132 }
133 if font.strikethrough {
134 writer.write_empty_element("strike", std::iter::empty::<(&str, &str)>())?;
135 }
136
137 match font.underline {
138 UnderlineStyle::None => {}
139 UnderlineStyle::Single => {
140 writer.write_empty_element("u", std::iter::empty::<(&str, &str)>())?
141 }
142 UnderlineStyle::Double => writer.write_empty_element("u", [("val", "double")])?,
143 UnderlineStyle::SingleAccounting => {
144 writer.write_empty_element("u", [("val", "singleAccounting")])?
145 }
146 UnderlineStyle::DoubleAccounting => {
147 writer.write_empty_element("u", [("val", "doubleAccounting")])?
148 }
149 }
150
151 match font.superscript {
152 SuperSub::None => {}
153 SuperSub::Superscript => {
154 writer.write_empty_element("vertAlign", [("val", "superscript")])?
155 }
156 SuperSub::Subscript => writer.write_empty_element("vertAlign", [("val", "subscript")])?,
157 }
158
159 writer.write_empty_element("sz", [("val", (font.size_twips as f64 / 20.0).to_string())])?;
160 write_color_element(writer, "color", &font.color)?;
161 writer.write_empty_element("name", [("val", font.name.as_str())])?;
162 writer.write_empty_element("family", [("val", font.family.to_string())])?;
163 if font.charset != 0 {
164 writer.write_empty_element("charset", [("val", font.charset.to_string())])?;
165 }
166
167 match font.scheme {
168 FontScheme::None => {}
169 FontScheme::Major => writer.write_empty_element("scheme", [("val", "major")])?,
170 FontScheme::Minor => writer.write_empty_element("scheme", [("val", "minor")])?,
171 }
172
173 writer.write_end_element("font")
174}
175
176fn write_fill<W: Write>(writer: &mut XmlWriter<W>, fill: &Fill) -> Result<()> {
177 writer.write_start_element("fill", std::iter::empty::<(&str, &str)>())?;
178 match &fill.pattern {
179 PatternFill::None => {
180 writer.write_empty_element("patternFill", [("patternType", "none")])?;
181 }
182 PatternFill::Solid { fg_color, bg_color } => {
183 writer.write_start_element("patternFill", [("patternType", "solid")])?;
184 write_color_element(writer, "fgColor", fg_color)?;
185 write_color_element(writer, "bgColor", bg_color)?;
186 writer.write_end_element("patternFill")?;
187 }
188 PatternFill::Pattern {
189 pattern_type,
190 fg_color,
191 bg_color,
192 } => {
193 let has_meaningful_colors = !is_default_color(fg_color) || !is_default_color(bg_color);
194 if has_meaningful_colors {
195 writer.write_start_element(
196 "patternFill",
197 [("patternType", pattern_type_to_str(*pattern_type))],
198 )?;
199 if !is_default_color(fg_color) {
200 write_color_element(writer, "fgColor", fg_color)?;
201 }
202 if !is_default_color(bg_color) {
203 write_color_element(writer, "bgColor", bg_color)?;
204 }
205 writer.write_end_element("patternFill")?;
206 } else {
207 writer.write_empty_element("patternFill", [("patternType", pattern_type_to_str(*pattern_type))])?;
208 }
209 }
210 PatternFill::Gradient(gradient) => {
211 write_gradient_fill(writer, gradient)?;
212 }
213 }
214 writer.write_end_element("fill")
215}
216
217fn write_gradient_fill<W: Write>(writer: &mut XmlWriter<W>, gradient: &GradientFill) -> Result<()> {
218 let mut attrs = vec![(
219 "type".to_string(),
220 match gradient.gradient_type {
221 GradientType::Linear => "linear".to_string(),
222 GradientType::Path => "path".to_string(),
223 },
224 )];
225
226 if gradient.degree != 0.0 {
227 attrs.push(("degree".to_string(), gradient.degree.to_string()));
228 }
229 if gradient.left != 0.0 {
230 attrs.push(("left".to_string(), gradient.left.to_string()));
231 }
232 if gradient.right != 0.0 {
233 attrs.push(("right".to_string(), gradient.right.to_string()));
234 }
235 if gradient.top != 0.0 {
236 attrs.push(("top".to_string(), gradient.top.to_string()));
237 }
238 if gradient.bottom != 0.0 {
239 attrs.push(("bottom".to_string(), gradient.bottom.to_string()));
240 }
241
242 writer.write_start_element("gradientFill", attrs)?;
243 for stop in &gradient.stops {
244 writer.write_start_element("stop", [("position", stop.position.to_string())])?;
245 write_color_element(writer, "color", &stop.color)?;
246 writer.write_end_element("stop")?;
247 }
248 writer.write_end_element("gradientFill")
249}
250
251fn write_border<W: Write>(writer: &mut XmlWriter<W>, border: &Border) -> Result<()> {
252 let mut attrs = Vec::new();
253 if border.diagonal_down {
254 attrs.push(("diagonalDown".to_string(), "1".to_string()));
255 }
256 if border.diagonal_up {
257 attrs.push(("diagonalUp".to_string(), "1".to_string()));
258 }
259 writer.write_start_element("border", attrs)?;
260
261 write_border_side(writer, "left", &border.left)?;
262 write_border_side(writer, "right", &border.right)?;
263 write_border_side(writer, "top", &border.top)?;
264 write_border_side(writer, "bottom", &border.bottom)?;
265 write_border_side(writer, "diagonal", &border.diagonal)?;
266
267 writer.write_end_element("border")
268}
269
270fn write_border_side<W: Write>(writer: &mut XmlWriter<W>, name: &str, side: &BorderSide) -> Result<()> {
271 if side.style == BorderStyle::None {
272 writer.write_empty_element(name, std::iter::empty::<(&str, &str)>())?;
273 return Ok(());
274 }
275
276 writer.write_start_element(name, [("style", border_style_to_str(side.style))])?;
277 write_color_element(writer, "color", &side.color)?;
278 writer.write_end_element(name)
279}
280
281fn write_xf<W: Write>(writer: &mut XmlWriter<W>, xf: &Xf) -> Result<()> {
282 let mut attrs = vec![
283 ("numFmtId".to_string(), xf.num_fmt_id.to_string()),
284 ("fontId".to_string(), xf.font_id.to_string()),
285 ("fillId".to_string(), xf.fill_id.to_string()),
286 ("borderId".to_string(), xf.border_id.to_string()),
287 ];
288
289 if let Some(xf_id) = xf.xf_id {
290 attrs.push(("xfId".to_string(), xf_id.to_string()));
291 }
292 if xf.apply_number_format {
293 attrs.push(("applyNumberFormat".to_string(), "1".to_string()));
294 }
295 if xf.apply_font {
296 attrs.push(("applyFont".to_string(), "1".to_string()));
297 }
298 if xf.apply_fill {
299 attrs.push(("applyFill".to_string(), "1".to_string()));
300 }
301 if xf.apply_border {
302 attrs.push(("applyBorder".to_string(), "1".to_string()));
303 }
304 if xf.apply_alignment {
305 attrs.push(("applyAlignment".to_string(), "1".to_string()));
306 }
307 if xf.apply_protection {
308 attrs.push(("applyProtection".to_string(), "1".to_string()));
309 }
310
311 let has_alignment = has_alignment(&xf.alignment);
312 let has_protection = xf.protection.locked || xf.protection.hidden;
313
314 if !has_alignment && !has_protection {
315 writer.write_empty_element("xf", attrs)?;
316 return Ok(());
317 }
318
319 writer.write_start_element("xf", attrs)?;
320 if has_alignment {
321 write_alignment(writer, &xf.alignment)?;
322 }
323 if has_protection {
324 let mut protection_attrs = Vec::new();
325 if xf.protection.locked {
326 protection_attrs.push(("locked".to_string(), "1".to_string()));
327 }
328 if xf.protection.hidden {
329 protection_attrs.push(("hidden".to_string(), "1".to_string()));
330 }
331 writer.write_empty_element("protection", protection_attrs)?;
332 }
333 writer.write_end_element("xf")
334}
335
336fn write_alignment<W: Write>(writer: &mut XmlWriter<W>, alignment: &Alignment) -> Result<()> {
337 let mut attrs = Vec::new();
338 if alignment.horizontal != HorizontalAlign::General {
339 attrs.push((
340 "horizontal".to_string(),
341 horizontal_to_str(alignment.horizontal).to_string(),
342 ));
343 }
344 if alignment.vertical != VerticalAlign::Bottom {
345 attrs.push((
346 "vertical".to_string(),
347 vertical_to_str(alignment.vertical).to_string(),
348 ));
349 }
350 if alignment.wrap_text {
351 attrs.push(("wrapText".to_string(), "1".to_string()));
352 }
353 if alignment.shrink_to_fit {
354 attrs.push(("shrinkToFit".to_string(), "1".to_string()));
355 }
356 if alignment.text_rotation != 0 {
357 attrs.push((
358 "textRotation".to_string(),
359 alignment.text_rotation.to_string(),
360 ));
361 }
362 if alignment.indent != 0 {
363 attrs.push(("indent".to_string(), alignment.indent.to_string()));
364 }
365 if alignment.reading_order != 0 {
366 attrs.push((
367 "readingOrder".to_string(),
368 alignment.reading_order.to_string(),
369 ));
370 }
371 writer.write_empty_element("alignment", attrs)
372}
373
374fn write_dxf<W: Write>(writer: &mut XmlWriter<W>, dxf: &DxfStyle) -> Result<()> {
375 writer.write_start_element("dxf", std::iter::empty::<(&str, &str)>())?;
376 if let Some(font) = &dxf.font {
377 write_font(writer, font)?;
378 }
379 if let Some(fill) = &dxf.fill {
380 write_fill(writer, fill)?;
381 }
382 if let Some(border) = &dxf.border {
383 write_border(writer, border)?;
384 }
385 if let Some(num_fmt) = &dxf.num_fmt {
386 writer.write_empty_element(
387 "numFmt",
388 [
389 ("numFmtId".to_string(), num_fmt.id.to_string()),
390 ("formatCode".to_string(), num_fmt.format_code.clone()),
391 ],
392 )?;
393 }
394 if let Some(alignment) = &dxf.alignment {
395 write_alignment(writer, alignment)?;
396 }
397 writer.write_end_element("dxf")
398}
399
400fn is_default_color(color: &Color) -> bool {
401 matches!(color.color_type, ColorType::Auto | ColorType::Indexed(64) | ColorType::Indexed(65))
402}
403
404fn write_color_element<W: Write>(writer: &mut XmlWriter<W>, name: &str, color: &Color) -> Result<()> {
405 match &color.color_type {
406 ColorType::Auto => writer.write_empty_element(name, [("auto", "1")]),
407 ColorType::Indexed(indexed) => {
408 writer.write_empty_element(name, [("indexed", indexed.to_string())])
409 }
410 ColorType::Rgb(a, r, g, b) => {
411 writer.write_empty_element(name, [("rgb", format!("{:02X}{:02X}{:02X}{:02X}", a, r, g, b))])
412 }
413 ColorType::Theme { theme, tint } => {
414 if *tint == 0.0 {
415 writer.write_empty_element(name, [("theme", theme.to_string())])
416 } else {
417 writer.write_empty_element(
418 name,
419 [
420 ("theme".to_string(), theme.to_string()),
421 ("tint".to_string(), tint.to_string()),
422 ],
423 )
424 }
425 }
426 }
427}
428
429fn has_alignment(alignment: &Alignment) -> bool {
430 alignment.horizontal != HorizontalAlign::General
431 || alignment.vertical != VerticalAlign::Bottom
432 || alignment.wrap_text
433 || alignment.shrink_to_fit
434 || alignment.text_rotation != 0
435 || alignment.indent != 0
436 || alignment.reading_order != 0
437}
438
439fn pattern_type_to_str(pattern_type: PatternType) -> &'static str {
440 match pattern_type {
441 PatternType::DarkDown => "darkDown",
442 PatternType::DarkGray => "darkGray",
443 PatternType::DarkGrid => "darkGrid",
444 PatternType::DarkHorizontal => "darkHorizontal",
445 PatternType::DarkTrellis => "darkTrellis",
446 PatternType::DarkUp => "darkUp",
447 PatternType::DarkVertical => "darkVertical",
448 PatternType::Gray0625 => "gray0625",
449 PatternType::Gray125 => "gray125",
450 PatternType::LightDown => "lightDown",
451 PatternType::LightGray => "lightGray",
452 PatternType::LightGrid => "lightGrid",
453 PatternType::LightHorizontal => "lightHorizontal",
454 PatternType::LightTrellis => "lightTrellis",
455 PatternType::LightUp => "lightUp",
456 PatternType::LightVertical => "lightVertical",
457 PatternType::MediumGray => "mediumGray",
458 }
459}
460
461fn border_style_to_str(style: BorderStyle) -> &'static str {
462 match style {
463 BorderStyle::None => "none",
464 BorderStyle::Thin => "thin",
465 BorderStyle::Medium => "medium",
466 BorderStyle::Dashed => "dashed",
467 BorderStyle::Dotted => "dotted",
468 BorderStyle::Thick => "thick",
469 BorderStyle::Double => "double",
470 BorderStyle::Hair => "hair",
471 BorderStyle::MediumDashed => "mediumDashed",
472 BorderStyle::DashDot => "dashDot",
473 BorderStyle::MediumDashDot => "mediumDashDot",
474 BorderStyle::DashDotDot => "dashDotDot",
475 BorderStyle::MediumDashDotDot => "mediumDashDotDot",
476 BorderStyle::SlantDashDot => "slantDashDot",
477 }
478}
479
480fn horizontal_to_str(horizontal: HorizontalAlign) -> &'static str {
481 match horizontal {
482 HorizontalAlign::General => "general",
483 HorizontalAlign::Left => "left",
484 HorizontalAlign::Center => "center",
485 HorizontalAlign::Right => "right",
486 HorizontalAlign::Fill => "fill",
487 HorizontalAlign::Justify => "justify",
488 HorizontalAlign::CenterContinuous => "centerContinuous",
489 HorizontalAlign::Distributed => "distributed",
490 }
491}
492
493fn vertical_to_str(vertical: VerticalAlign) -> &'static str {
494 match vertical {
495 VerticalAlign::Top => "top",
496 VerticalAlign::Center => "center",
497 VerticalAlign::Bottom => "bottom",
498 VerticalAlign::Justify => "justify",
499 VerticalAlign::Distributed => "distributed",
500 }
501}