NanoXLSX.Writer 3.0.0-rc.3
Loading...
Searching...
No Matches
StyleWriter.cs
1/*
2 * NanoXLSX is a small .NET library to generate and read XLSX (Microsoft Excel 2007 or newer) files in an easy and native way
3 * Copyright Raphael Stoeckli © 2025
4 * This library is licensed under the MIT License.
5 * You find a copy of the license in project folder or on: http://opensource.org/licenses/MIT
6 */
7
8using NanoXLSX.Colors;
9using NanoXLSX.Exceptions;
11using NanoXLSX.Registry;
12using NanoXLSX.Registry.Attributes;
13using NanoXLSX.Styles;
14using NanoXLSX.Utils;
15using NanoXLSX.Utils.Xml;
16using System.Collections.Generic;
17using System.Linq;
18using static NanoXLSX.Styles.Border;
19using static NanoXLSX.Styles.CellXf;
20using static NanoXLSX.Styles.Fill;
21using static NanoXLSX.Styles.Font;
22using static NanoXLSX.Styles.NumberFormat;
23
25{
29 [NanoXlsxPlugIn(PlugInUUID = PlugInUUID.StyleWriter)]
30 internal class StyleWriter : IPluginWriter
31 {
32
33 private StyleManager styles;
34 private XmlElement styleSheet;
35 private IColorWriter colorWriter;
36
37 #region properties
41 public Workbook Workbook { get; set; }
42
46 public XmlElement XmlElement { get => styleSheet; }
47
48 #endregion
49 #region constructors
53 internal StyleWriter()
54 {
55 }
56
57 #endregion
58 #region methods
63 public void Init(IBaseWriter baseWriter)
64 {
65 this.styles = baseWriter.Styles;
66 this.Workbook = baseWriter.Workbook;
67 this.colorWriter = PlugInLoader.GetPlugIn<IColorWriter>(PlugInUUID.ColorWriter, new ColorWriter());
68 }
69
73 public void Execute()
74 {
75 styleSheet = XmlElement.CreateElement("styleSheet");
76 styleSheet.AddDefaultXmlNameSpace("http://schemas.openxmlformats.org/spreadsheetml/2006/main");
77 styleSheet.AddNameSpaceAttribute("mc", "xmlns", "http://schemas.openxmlformats.org/markup-compatibility/2006");
78 styleSheet.AddNameSpaceAttribute("x14ac", "xmlns", "http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac");
79 styleSheet.AddAttribute("mc:Ignorable", "x14ac");
80 int numFormatCount = styles.GetNumberFormatStyleNumber();
81 int fontCount = styles.GetFontStyleNumber();
82 int fillCount = styles.GetFillStyleNumber();
83 int borderCount = styles.GetBorderStyleNumber();
84 int styleCount = styles.GetStyleNumber();
85 if (numFormatCount > 0)
86 {
87 XmlElement numFmts = XmlElement.CreateElementWithAttribute("numFmts", "count", ParserUtils.ToString(numFormatCount));
88 numFmts.AddChildElements(GetNumberFormatElements());
89 styleSheet.AddChildElement(numFmts);
90 }
91 XmlElement fonts = XmlElement.CreateElementWithAttribute("fonts", "count", ParserUtils.ToString(fontCount));
92 fonts.AddAttribute("knownFonts", "1", "x14ac");
93 fonts.AddChildElements(GetFontElements());
94 styleSheet.AddChildElement(fonts);
95 XmlElement fills = XmlElement.CreateElementWithAttribute("fills", "count", ParserUtils.ToString(fillCount));
96 fills.AddChildElements(GetFillElements());
97 styleSheet.AddChildElement(fills);
98 XmlElement borders = XmlElement.CreateElementWithAttribute("borders", "count", ParserUtils.ToString(borderCount));
99 borders.AddChildElements(GetBorderElements());
100 styleSheet.AddChildElement(borders);
101 XmlElement cellXfs = XmlElement.CreateElementWithAttribute("cellXfs", "count", ParserUtils.ToString(styleCount));
102 cellXfs.AddChildElements(GetCellXfElements());
103 styleSheet.AddChildElement(cellXfs);
104 if (Workbook.WorkbookMetadata != null)
105 {
106 XmlElement mruElement = GetMruElement();
107 if (mruElement != null)
108 {
109 XmlElement colors = styleSheet.AddChildElement("colors");
110 colors.AddChildElement(mruElement);
111 }
112 }
113
114 WriterPlugInHandler.HandleInlineQueuePlugins(ref styleSheet, Workbook, PlugInUUID.StyleInlineWriter);
115 }
116
121 private List<XmlElement> GetBorderElements()
122 {
123 Border[] borderStyles = styles.GetBorders();
124 List<XmlElement> borders = new List<XmlElement>(borderStyles.Length);
125 foreach (Border item in borderStyles)
126 {
127 XmlElement border = XmlElement.CreateElement("border");
128 if (item.DiagonalDown && !item.DiagonalUp) { border.AddAttribute("diagonalDown", "1"); }
129 else if (!item.DiagonalDown && item.DiagonalUp) { border.AddAttribute("diagonalUp", "1"); }
130 else if (item.DiagonalDown && item.DiagonalUp)
131 {
132 border.AddAttribute("diagonalDown", "1");
133 border.AddAttribute("diagonalUp", "1");
134 }
135 if (item.LeftStyle != StyleValue.None)
136 {
137 XmlElement left = border.AddChildElementWithAttribute("left", "style", Border.GetStyleName(item.LeftStyle));
138 if (!string.IsNullOrEmpty(item.LeftColor)) { left.AddChildElementWithAttribute("color", "rgb", item.LeftColor); }
139 else { left.AddChildElementWithAttribute("color", "auto", "1"); }
140 }
141 else
142 {
143 border.AddChildElement("left");
144 }
145 if (item.RightStyle != StyleValue.None)
146 {
147 XmlElement right = border.AddChildElementWithAttribute("right", "style", Border.GetStyleName(item.RightStyle));
148 if (!string.IsNullOrEmpty(item.RightColor)) { right.AddChildElementWithAttribute("color", "rgb", item.RightColor); }
149 else { right.AddChildElementWithAttribute("color", "auto", "1"); }
150 }
151 else
152 {
153 border.AddChildElement("right");
154 }
155 if (item.TopStyle != StyleValue.None)
156 {
157 XmlElement top = border.AddChildElementWithAttribute("top", "style", Border.GetStyleName(item.TopStyle));
158 if (!string.IsNullOrEmpty(item.TopColor)) { top.AddChildElementWithAttribute("color", "rgb", item.TopColor); }
159 else { top.AddChildElementWithAttribute("color", "auto", "1"); }
160 }
161 else
162 {
163 border.AddChildElement("top");
164 }
165 if (item.BottomStyle != StyleValue.None)
166 {
167 XmlElement bottom = border.AddChildElementWithAttribute("bottom", "style", Border.GetStyleName(item.BottomStyle));
168 if (!string.IsNullOrEmpty(item.BottomColor)) { bottom.AddChildElementWithAttribute("color", "rgb", item.BottomColor); }
169 else { bottom.AddChildElementWithAttribute("color", "auto", "1"); }
170 }
171 else
172 {
173 border.AddChildElement("bottom");
174 }
175 if (item.DiagonalStyle != StyleValue.None)
176 {
177 XmlElement diagonal = border.AddChildElementWithAttribute("diagonal", "style", Border.GetStyleName(item.DiagonalStyle));
178 if (!string.IsNullOrEmpty(item.DiagonalColor)) { diagonal.AddChildElementWithAttribute("color", "rgb", item.DiagonalColor); }
179 else { diagonal.AddChildElementWithAttribute("color", "auto", "1"); }
180 }
181 else
182 {
183 border.AddChildElement("diagonal");
184 }
185 borders.Add(border);
186 }
187 return borders;
188 }
189
194 private List<XmlElement> GetFontElements()
195 {
196 Font[] fontStyles = styles.GetFonts();
197 List<XmlElement> fonts = new List<XmlElement>(fontStyles.Length);
198 foreach (Font item in fontStyles)
199 {
200 XmlElement font = XmlElement.CreateElement("font");
201 if (item.Bold) { font.AddChildElement("b"); }
202 if (item.Italic) { font.AddChildElement("i"); }
203 if (item.Strike) { font.AddChildElement("strike"); }
204 if (item.Outline) { font.AddChildElement("outline"); }
205 if (item.Shadow) { font.AddChildElement("shadow"); }
206 if (item.Condense) { font.AddChildElement("condense"); }
207 if (item.Extend) { font.AddChildElement("extend"); }
208 if (item.Underline != UnderlineValue.None && item.Underline != UnderlineValue.Single)
209 {
210 font.AddChildElementWithAttribute("u", "val", Font.GetUnderlineName(item.Underline));
211 }
212 else if (item.Underline == UnderlineValue.Single)
213 {
214 font.AddChildElement("u");
215 }
216 if (item.VerticalAlign != VerticalTextAlignValue.None)
217 {
218 font.AddChildElementWithAttribute("vertAlign", "val", Font.GetVerticalTextAlignName(item.VerticalAlign));
219 }
220 font.AddChildElementWithAttribute("sz", "val", ParserUtils.ToString(item.Size));
221 if (item.ColorValue != null && item.ColorValue.IsDefined)
222 {
223 XmlElement color = XmlElement.CreateElement("color");
224 color.AddAttributes(colorWriter.GetAttributes(item.ColorValue));
225 font.AddChildElement(color);
226 }
227 font.AddChildElementWithAttribute("name", "val", item.Name);
228 font.AddChildElementWithAttribute("family", "val", ParserUtils.ToString((int)item.Family));
229 if (item.Scheme != SchemeValue.None)
230 {
231 if (item.Scheme == SchemeValue.Major)
232 { font.AddChildElementWithAttribute("scheme", "val", "major"); }
233 else if (item.Scheme == SchemeValue.Minor)
234 { font.AddChildElementWithAttribute("scheme", "val", "minor"); }
235 }
236 font.AddChildElementWithAttribute("charset", "val", ParserUtils.ToString((int)item.Charset));
237 fonts.Add(font);
238 }
239 return fonts;
240 }
241
246 private List<XmlElement> GetFillElements()
247 {
248 Fill[] fillStyles = styles.GetFills();
249 List<XmlElement> fills = new List<XmlElement>(fillStyles.Length);
250 foreach (Fill fill in fillStyles)
251 {
252 XmlElement fillElement = XmlElement.CreateElement("fill");
253 XmlElement patternFillElement = fillElement.AddChildElement("patternFill");
254 patternFillElement.AddAttribute("patternType", Fill.GetPatternName(fill.PatternFill));
255 if (fill.PatternFill == PatternValue.Solid)
256 {
257 XmlElement fgColor = XmlElement.CreateElement("fgColor");
258 fgColor.AddAttributes(colorWriter.GetAttributes(fill.ForegroundColor));
259 patternFillElement.AddChildElement(fgColor);
260
261 // Only add bgColor if explicitly defined OR required by Excel
262 if (fill.BackgroundColor.IsDefined)
263 {
264 XmlElement bgColor = XmlElement.CreateElement("bgColor");
265 bgColor.AddAttributes(colorWriter.GetAttributes(fill.BackgroundColor));
266 patternFillElement.AddChildElement(bgColor);
267 }
268 else if (fill.ForegroundColor.Type == Color.ColorType.Rgb
269 || fill.ForegroundColor.Type == Color.ColorType.Theme)
270 {
271 // Excel compatibility bgColor
272 XmlElement bgColor = XmlElement.CreateElement("bgColor");
273 bgColor.AddAttributes(
274 colorWriter.GetAttributes(
275 Color.CreateIndexed(IndexedColor.DefaultIndexedColor)
276 ));
277 patternFillElement.AddChildElement(bgColor);
278 }
279 }
280 else if (fill.PatternFill != PatternValue.None)
281 {
282 XmlElement fgColor = XmlElement.CreateElement("fgColor");
283 fgColor.AddAttributes(colorWriter.GetAttributes(fill.ForegroundColor));
284 patternFillElement.AddChildElement(fgColor);
285 XmlElement bgColor = XmlElement.CreateElement("bgColor");
286 bgColor.AddAttributes(colorWriter.GetAttributes(fill.BackgroundColor));
287 patternFillElement.AddChildElement(bgColor);
288 }
289 fills.Add(fillElement);
290 }
291 return fills;
292 }
293
298 private List<XmlElement> GetNumberFormatElements()
299 {
300 NumberFormat[] numberFormatStyles = styles.GetNumberFormats();
301 List<XmlElement> elements = new List<XmlElement>(numberFormatStyles.Length);
302 foreach (NumberFormat item in numberFormatStyles)
303 {
304 if (item.IsCustomFormat)
305 {
306 if (string.IsNullOrEmpty(item.CustomFormatCode))
307 {
308 throw new FormatException("The number format style component with the ID " + ParserUtils.ToString(item.CustomFormatID) + " cannot be null or empty");
309 }
310 // OOXML: Escaping according to Chp.18.8.31
311 // TODO: v3> Add a custom format builder
312 XmlElement element = XmlElement.CreateElementWithAttribute("numFmt", "formatCode", XmlUtils.SanitizeXmlValue(item.CustomFormatCode));
313 element.AddAttribute("numFmtId", ParserUtils.ToString(item.CustomFormatID));
314 elements.Add(element);
315 }
316 }
317 return elements;
318 }
319
324 private List<XmlElement> GetCellXfElements()
325 {
326 Style[] styleItems = this.styles.GetStyles();
327 List<XmlElement> xfs = new List<XmlElement>(styleItems.Length);
328 foreach (Style style in styleItems)
329 {
330 int textRotation = style.CurrentCellXf.CalculateInternalRotation();
331 XmlElement alignment = null;
332 XmlElement protection = null;
333 if (style.CurrentCellXf.HorizontalAlign != HorizontalAlignValue.None || style.CurrentCellXf.VerticalAlign != VerticalAlignValue.None || style.CurrentCellXf.Alignment != TextBreakValue.None || textRotation != 0)
334 {
335 alignment = XmlElement.CreateElement("alignment");
336 if (style.CurrentCellXf.HorizontalAlign != HorizontalAlignValue.None)
337 {
338 string alignValue = CellXf.GetHorizontalAlignName(style.CurrentCellXf.HorizontalAlign);
339 alignment.AddAttribute("horizontal", alignValue);
340 }
341 if (style.CurrentCellXf.VerticalAlign != VerticalAlignValue.None)
342 {
343 string alignValue = CellXf.GetVerticalAlignName(style.CurrentCellXf.VerticalAlign);
344 alignment.AddAttribute("vertical", alignValue);
345 }
346 if (style.CurrentCellXf.Indent > 0 &&
347 (style.CurrentCellXf.HorizontalAlign == HorizontalAlignValue.Left
348 || style.CurrentCellXf.HorizontalAlign == HorizontalAlignValue.Right
349 || style.CurrentCellXf.HorizontalAlign == HorizontalAlignValue.Distributed))
350 {
351 alignment.AddAttribute("indent", ParserUtils.ToString(style.CurrentCellXf.Indent));
352 }
353 if (style.CurrentCellXf.Alignment != TextBreakValue.None)
354 {
355 if (style.CurrentCellXf.Alignment == TextBreakValue.ShrinkToFit) { alignment.AddAttribute("shrinkToFit", "1"); }
356 else { alignment.AddAttribute("wrapText", "1"); }
357 }
358 if (textRotation != 0)
359 {
360 alignment.AddAttribute("textRotation", ParserUtils.ToString(textRotation));
361 }
362 }
363 if (!style.CurrentCellXf.Locked || style.CurrentCellXf.Hidden)
364 {
365 protection = XmlElement.CreateElement("protection");
366 if (style.CurrentCellXf.Hidden && style.CurrentCellXf.Locked)
367 {
368 protection.AddAttribute("hidden", "1"); // Locked is true by default (no need to define)
369 }
370 else if (style.CurrentCellXf.Hidden && !style.CurrentCellXf.Locked)
371 {
372 protection.AddAttribute("hidden", "1");
373 protection.AddAttribute("locked", "0");
374 }
375 else if (!style.CurrentCellXf.Hidden && !style.CurrentCellXf.Locked)
376 {
377 protection.AddAttribute("locked", "0"); // To be defined, since locked is true by default (no need for hidden, since false by default)
378
379 }
380 }
381 else
382 {
383 protection = null; // No definition if only locked == true
384 }
385 XmlElement xf = XmlElement.CreateElement("xf");
386 if (style.CurrentNumberFormat.IsCustomFormat)
387 {
388 xf.AddAttribute("numFmtId", ParserUtils.ToString(style.CurrentNumberFormat.CustomFormatID));
389 }
390 else
391 {
392 int formatNumber = (int)style.CurrentNumberFormat.Number;
393 xf.AddAttribute("numFmtId", ParserUtils.ToString(formatNumber));
394 }
395 xf.AddAttribute("borderId", ParserUtils.ToString(style.CurrentBorder.InternalID.Value));
396 xf.AddAttribute("fillId", ParserUtils.ToString(style.CurrentFill.InternalID.Value));
397 xf.AddAttribute("fontId", ParserUtils.ToString(style.CurrentFont.InternalID.Value));
398 if (!style.CurrentFont.IsDefaultFont)
399 {
400 xf.AddAttribute("applyFont", "1");
401 }
402 if (style.CurrentFill.PatternFill != PatternValue.None)
403 {
404 xf.AddAttribute("applyFill", "1");
405 }
406 if (!style.CurrentBorder.IsEmpty())
407 {
408 xf.AddAttribute("applyBorder", "1");
409 }
410 if (alignment != null || style.CurrentCellXf.ForceApplyAlignment)
411 {
412 xf.AddAttribute("applyAlignment", "1");
413 }
414 if (protection != null)
415 {
416 xf.AddAttribute("applyProtection", "1");
417 }
418 if (style.CurrentNumberFormat.Number != FormatNumber.None)
419 {
420 xf.AddAttribute("applyNumberFormat", "1");
421 }
422 if (alignment != null || protection != null)
423 {
424 xf.AddChildElement(alignment);
425 xf.AddChildElement(protection);
426 }
427 xfs.Add(xf);
428 }
429 return xfs;
430 }
431
436 private XmlElement GetMruElement()
437 {
438 XmlElement mruColors = null;
439 List<SrgbColor> tempColors = new List<SrgbColor>();
440 foreach (Color color in Workbook.GetMruColors())
441 {
442 if (!color.IsDefined || (color.Type != Color.ColorType.Rgb && color.Type != Color.ColorType.Indexed))
443 {
444 continue;
445 }
446 SrgbColor tempColor = null;
447 if (color.Type == Color.ColorType.Rgb)
448 {
449 tempColor = color.RgbColor;
450 }
451 else // Indexed
452 {
453 tempColor = color.IndexedColor.GetSrgbColor();
454 }
455 if (tempColor.StringValue == SrgbColor.DefaultSrgbColor)
456 {
457 continue;
458 }
459 if (!tempColors.Any(c => c.StringValue == tempColor.StringValue))
460 {
461 tempColors.Add(tempColor);
462 }
463 }
464 if (tempColors.Count > 0)
465 {
466 mruColors = XmlElement.CreateElement("mruColors");
467 foreach (SrgbColor item in tempColors)
468 {
469 mruColors.AddChildElementWithAttribute("color", "rgb", item.ColorValue);
470 }
471 }
472 return mruColors;
473 }
474 #endregion
475 }
476}