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.Exceptions;
10using NanoXLSX.Registry;
11using NanoXLSX.Registry.Attributes;
12using NanoXLSX.Styles;
13using NanoXLSX.Utils;
14using NanoXLSX.Utils.Xml;
15using System.Collections.Generic;
16using static NanoXLSX.Styles.Border;
17using static NanoXLSX.Styles.CellXf;
18using static NanoXLSX.Styles.Fill;
19using static NanoXLSX.Styles.Font;
20using static NanoXLSX.Styles.NumberFormat;
21
23{
27 [NanoXlsxPlugIn(PlugInUUID = PlugInUUID.StyleWriter)]
28 internal class StyleWriter : IPlugInWriter
29 {
30
31 private StyleManager styles;
32 private XmlElement styleSheet;
33
34 #region properties
38 public Workbook Workbook { get; set; }
39
43 public XmlElement XmlElement { get => styleSheet; }
44
45 #endregion
46 #region constructors
50 internal StyleWriter()
51 {
52 }
53
54 #endregion
55 #region methods
60 public void Init(IBaseWriter baseWriter)
61 {
62 this.styles = baseWriter.Styles;
63 this.Workbook = baseWriter.Workbook;
64 }
65
69 public void Execute()
70 {
71 styleSheet = XmlElement.CreateElement("styleSheet");
72 styleSheet.AddDefaultXmlNameSpace("http://schemas.openxmlformats.org/spreadsheetml/2006/main");
73 styleSheet.AddNameSpaceAttribute("mc", "xmlns", "http://schemas.openxmlformats.org/markup-compatibility/2006");
74 styleSheet.AddNameSpaceAttribute("x14ac", "xmlns", "http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac");
75 styleSheet.AddAttribute("mc:Ignorable", "x14ac");
76 int numFormatCount = styles.GetNumberFormatStyleNumber();
77 int fontCount = styles.GetFontStyleNumber();
78 int fillCount = styles.GetFillStyleNumber();
79 int borderCount = styles.GetBorderStyleNumber();
80 int styleCount = styles.GetStyleNumber();
81 if (numFormatCount > 0)
82 {
83 XmlElement numFmts = XmlElement.CreateElementWithAttribute("numFmts", "count", ParserUtils.ToString(numFormatCount));
84 numFmts.AddChildElements(GetNumberFormatElements());
85 styleSheet.AddChildElement(numFmts);
86 }
87 XmlElement fonts = XmlElement.CreateElementWithAttribute("fonts", "count", ParserUtils.ToString(fontCount));
88 fonts.AddAttribute("knownFonts", "1", "x14ac");
89 fonts.AddChildElements(GetFontElements());
90 styleSheet.AddChildElement(fonts);
91 XmlElement fills = XmlElement.CreateElementWithAttribute("fills", "count", ParserUtils.ToString(fillCount));
92 fills.AddChildElements(GetFillElements());
93 styleSheet.AddChildElement(fills);
94 XmlElement borders = XmlElement.CreateElementWithAttribute("borders", "count", ParserUtils.ToString(borderCount));
95 borders.AddChildElements(GetBorderElements());
96 styleSheet.AddChildElement(borders);
97 XmlElement cellXfs = XmlElement.CreateElementWithAttribute("cellXfs", "count", ParserUtils.ToString(styleCount));
98 cellXfs.AddChildElements(GetCellXfElements());
99 styleSheet.AddChildElement(cellXfs);
100 if (Workbook.WorkbookMetadata != null)
101 {
102 XmlElement mruElement = GetMruElement();
103 if (mruElement != null)
104 {
105 XmlElement colors = styleSheet.AddChildElement("colors");
106 colors.AddChildElement(mruElement);
107 }
108 }
109
110 WriterPlugInHandler.HandleInlineQueuePlugins(ref styleSheet, Workbook, PlugInUUID.StyleInlineWriter);
111 }
112
117 private List<XmlElement> GetBorderElements()
118 {
119 Border[] borderStyles = styles.GetBorders();
120 List<XmlElement> borders = new List<XmlElement>(borderStyles.Length);
121 foreach (Border item in borderStyles)
122 {
123 XmlElement border = XmlElement.CreateElement("border");
124 if (item.DiagonalDown && !item.DiagonalUp) { border.AddAttribute("diagonalDown", "1"); }
125 else if (!item.DiagonalDown && item.DiagonalUp) { border.AddAttribute("diagonalUp", "1"); }
126 else if (item.DiagonalDown && item.DiagonalUp)
127 {
128 border.AddAttribute("diagonalDown", "1");
129 border.AddAttribute("diagonalUp", "1");
130 }
131 if (item.LeftStyle != StyleValue.None)
132 {
133 XmlElement left = border.AddChildElementWithAttribute("left", "style", Border.GetStyleName(item.LeftStyle));
134 if (!string.IsNullOrEmpty(item.LeftColor)) { left.AddChildElementWithAttribute("color", "rgb", item.LeftColor); }
135 else { left.AddChildElementWithAttribute("color", "auto", "1"); }
136 }
137 else
138 {
139 border.AddChildElement("left");
140 }
141 if (item.RightStyle != StyleValue.None)
142 {
143 XmlElement right = border.AddChildElementWithAttribute("right", "style", Border.GetStyleName(item.RightStyle));
144 if (!string.IsNullOrEmpty(item.RightColor)) { right.AddChildElementWithAttribute("color", "rgb", item.RightColor); }
145 else { right.AddChildElementWithAttribute("color", "auto", "1"); }
146 }
147 else
148 {
149 border.AddChildElement("right");
150 }
151 if (item.TopStyle != StyleValue.None)
152 {
153 XmlElement top = border.AddChildElementWithAttribute("top", "style", Border.GetStyleName(item.TopStyle));
154 if (!string.IsNullOrEmpty(item.TopColor)) { top.AddChildElementWithAttribute("color", "rgb", item.TopColor); }
155 else { top.AddChildElementWithAttribute("color", "auto", "1"); }
156 }
157 else
158 {
159 border.AddChildElement("top");
160 }
161 if (item.BottomStyle != StyleValue.None)
162 {
163 XmlElement bottom = border.AddChildElementWithAttribute("bottom", "style", Border.GetStyleName(item.BottomStyle));
164 if (!string.IsNullOrEmpty(item.BottomColor)) { bottom.AddChildElementWithAttribute("color", "rgb", item.BottomColor); }
165 else { bottom.AddChildElementWithAttribute("color", "auto", "1"); }
166 }
167 else
168 {
169 border.AddChildElement("bottom");
170 }
171 if (item.DiagonalStyle != StyleValue.None)
172 {
173 XmlElement diagonal = border.AddChildElementWithAttribute("diagonal", "style", Border.GetStyleName(item.DiagonalStyle));
174 if (!string.IsNullOrEmpty(item.DiagonalColor)) { diagonal.AddChildElementWithAttribute("color", "rgb", item.DiagonalColor); }
175 else { diagonal.AddChildElementWithAttribute("color", "auto", "1"); }
176 }
177 else
178 {
179 border.AddChildElement("diagonal");
180 }
181 borders.Add(border);
182 }
183 return borders;
184 }
185
190 private List<XmlElement> GetFontElements()
191 {
192 Font[] fontStyles = styles.GetFonts();
193 List<XmlElement> fonts = new List<XmlElement>(fontStyles.Length);
194 foreach (Font item in fontStyles)
195 {
196 XmlElement font = XmlElement.CreateElement("font");
197 if (item.Bold) { font.AddChildElement("b"); }
198 if (item.Italic) { font.AddChildElement("i"); }
199 if (item.Strike) { font.AddChildElement("strike"); }
200 if (item.Underline != UnderlineValue.None && item.Underline != UnderlineValue.Single)
201 {
202 font.AddChildElementWithAttribute("u", "val", Font.GetUnderlineName(item.Underline));
203 }
204 else if (item.Underline == UnderlineValue.Single)
205 {
206 font.AddChildElement("u");
207 }
208 if (item.VerticalAlign != VerticalTextAlignValue.None)
209 {
210 font.AddChildElementWithAttribute("vertAlign", "val", Font.GetVerticalTextAlignName(item.VerticalAlign));
211 }
212 font.AddChildElementWithAttribute("sz", "val", ParserUtils.ToString(item.Size));
213 if (string.IsNullOrEmpty(item.ColorValue))
214 {
215 font.AddChildElementWithAttribute("color", "theme", ParserUtils.ToString((int)item.ColorTheme));
216 }
217 else
218 {
219 font.AddChildElementWithAttribute("color", "rgb", item.ColorValue);
220 }
221 font.AddChildElementWithAttribute("name", "val", item.Name);
222 font.AddChildElementWithAttribute("family", "val", ParserUtils.ToString((int)item.Family));
223 if (item.Scheme != SchemeValue.None)
224 {
225 if (item.Scheme == SchemeValue.Major)
226 { font.AddChildElementWithAttribute("scheme", "val", "major"); }
227 else if (item.Scheme == SchemeValue.Minor)
228 { font.AddChildElementWithAttribute("scheme", "val", "minor"); }
229 }
230 font.AddChildElementWithAttribute("charset", "val", ParserUtils.ToString((int)item.Charset));
231 fonts.Add(font);
232 }
233 return fonts;
234 }
235
240 private List<XmlElement> GetFillElements()
241 {
242 Fill[] fillStyles = styles.GetFills();
243 List<XmlElement> fills = new List<XmlElement>(fillStyles.Length);
244 foreach (Fill item in fillStyles)
245 {
246 XmlElement fill = XmlElement.CreateElement("fill");
247 XmlElement patternFill = fill.AddChildElement("patternFill");
248 patternFill.AddAttribute("patternType", Fill.GetPatternName(item.PatternFill));
249 if (item.PatternFill == PatternValue.Solid)
250 {
251 patternFill.AddChildElementWithAttribute("fgColor", "rgb", item.ForegroundColor);
252 patternFill.AddChildElementWithAttribute("bgColor", "indexed", ParserUtils.ToString(item.IndexedColor));
253 }
254 else if (item.PatternFill == PatternValue.MediumGray || item.PatternFill == PatternValue.LightGray || item.PatternFill == PatternValue.Gray0625 || item.PatternFill == PatternValue.DarkGray)
255 {
256 patternFill.AddChildElementWithAttribute("fgColor", "rgb", item.ForegroundColor);
257 if (!string.IsNullOrEmpty(item.BackgroundColor))
258 {
259 patternFill.AddChildElementWithAttribute("bgColor", "rgb", item.BackgroundColor);
260 }
261 }
262 fills.Add(fill);
263 }
264 return fills;
265 }
266
271 private List<XmlElement> GetNumberFormatElements()
272 {
273 NumberFormat[] numberFormatStyles = styles.GetNumberFormats();
274 List<XmlElement> elements = new List<XmlElement>(numberFormatStyles.Length);
275 foreach (NumberFormat item in numberFormatStyles)
276 {
277 if (item.IsCustomFormat)
278 {
279 if (string.IsNullOrEmpty(item.CustomFormatCode))
280 {
281 throw new FormatException("The number format style component with the ID " + ParserUtils.ToString(item.CustomFormatID) + " cannot be null or empty");
282 }
283 // OOXML: Escaping according to Chp.18.8.31
284 // TODO: v3> Add a custom format builder
285 XmlElement element = XmlElement.CreateElementWithAttribute("numFmt", "formatCode", XmlUtils.SanitizeXmlValue(item.CustomFormatCode));
286 element.AddAttribute("numFmtId", ParserUtils.ToString(item.CustomFormatID));
287 elements.Add(element);
288 }
289 }
290 return elements;
291 }
292
297 private List<XmlElement> GetCellXfElements()
298 {
299 Style[] styleItems = this.styles.GetStyles();
300 List<XmlElement> xfs = new List<XmlElement>(styleItems.Length);
301 foreach (Style style in styleItems)
302 {
303 int textRotation = style.CurrentCellXf.CalculateInternalRotation();
304 XmlElement alignment = null;
305 XmlElement protection = null;
306 if (style.CurrentCellXf.HorizontalAlign != HorizontalAlignValue.None || style.CurrentCellXf.VerticalAlign != VerticalAlignValue.None || style.CurrentCellXf.Alignment != TextBreakValue.None || textRotation != 0)
307 {
308 alignment = XmlElement.CreateElement("alignment");
309 if (style.CurrentCellXf.HorizontalAlign != HorizontalAlignValue.None)
310 {
311 string alignValue = CellXf.GetHorizontalAlignName(style.CurrentCellXf.HorizontalAlign);
312 alignment.AddAttribute("horizontal", alignValue);
313 }
314 if (style.CurrentCellXf.VerticalAlign != VerticalAlignValue.None)
315 {
316 string alignValue = CellXf.GetVerticalAlignName(style.CurrentCellXf.VerticalAlign);
317 alignment.AddAttribute("vertical", alignValue);
318 }
319 if (style.CurrentCellXf.Indent > 0 &&
320 (style.CurrentCellXf.HorizontalAlign == HorizontalAlignValue.Left
321 || style.CurrentCellXf.HorizontalAlign == HorizontalAlignValue.Right
322 || style.CurrentCellXf.HorizontalAlign == HorizontalAlignValue.Distributed))
323 {
324 alignment.AddAttribute("indent", ParserUtils.ToString(style.CurrentCellXf.Indent));
325 }
326 if (style.CurrentCellXf.Alignment != TextBreakValue.None)
327 {
328 if (style.CurrentCellXf.Alignment == TextBreakValue.ShrinkToFit) { alignment.AddAttribute("shrinkToFit", "1"); }
329 else { alignment.AddAttribute("wrapText", "1"); }
330 }
331 if (textRotation != 0)
332 {
333 alignment.AddAttribute("textRotation", ParserUtils.ToString(textRotation));
334 }
335 }
336 if (!style.CurrentCellXf.Locked || style.CurrentCellXf.Hidden)
337 {
338 protection = XmlElement.CreateElement("protection");
339 if (style.CurrentCellXf.Hidden && style.CurrentCellXf.Locked)
340 {
341 protection.AddAttribute("hidden", "1"); // Locked is true by default (no need to define)
342 }
343 else if (style.CurrentCellXf.Hidden && !style.CurrentCellXf.Locked)
344 {
345 protection.AddAttribute("hidden", "1");
346 protection.AddAttribute("locked", "0");
347 }
348 else if (!style.CurrentCellXf.Hidden && !style.CurrentCellXf.Locked)
349 {
350 protection.AddAttribute("locked", "0"); // To be defined, since locked is true by default (no need for hidden, since false by default)
351
352 }
353 }
354 else
355 {
356 protection = null; // No definition if only locked == true
357 }
358 XmlElement xf = XmlElement.CreateElement("xf");
359 if (style.CurrentNumberFormat.IsCustomFormat)
360 {
361 xf.AddAttribute("numFmtId", ParserUtils.ToString(style.CurrentNumberFormat.CustomFormatID));
362 }
363 else
364 {
365 int formatNumber = (int)style.CurrentNumberFormat.Number;
366 xf.AddAttribute("numFmtId", ParserUtils.ToString(formatNumber));
367 }
368 xf.AddAttribute("borderId", ParserUtils.ToString(style.CurrentBorder.InternalID.Value));
369 xf.AddAttribute("fillId", ParserUtils.ToString(style.CurrentFill.InternalID.Value));
370 xf.AddAttribute("fontId", ParserUtils.ToString(style.CurrentFont.InternalID.Value));
371 if (!style.CurrentFont.IsDefaultFont)
372 {
373 xf.AddAttribute("applyFont", "1");
374 }
375 if (style.CurrentFill.PatternFill != PatternValue.None)
376 {
377 xf.AddAttribute("applyFill", "1");
378 }
379 if (!style.CurrentBorder.IsEmpty())
380 {
381 xf.AddAttribute("applyBorder", "1");
382 }
383 if (alignment != null || style.CurrentCellXf.ForceApplyAlignment)
384 {
385 xf.AddAttribute("applyAlignment", "1");
386 }
387 if (protection != null)
388 {
389 xf.AddAttribute("applyProtection", "1");
390 }
391 if (style.CurrentNumberFormat.Number != FormatNumber.None)
392 {
393 xf.AddAttribute("applyNumberFormat", "1");
394 }
395 if (alignment != null || protection != null)
396 {
397 xf.AddChildElement(alignment);
398 xf.AddChildElement(protection);
399 }
400 xfs.Add(xf);
401 }
402 return xfs;
403 }
404
409 private XmlElement GetMruElement()
410 {
411 XmlElement mruColors = null;
412 List<string> tempColors = new List<string>();
413 foreach (string item in ((Workbook)this.Workbook).GetMruColors())
414 {
415 if (item == Fill.DefaultColor)
416 {
417 continue;
418 }
419 if (!tempColors.Contains(item)) { tempColors.Add(item); }
420 }
421 if (tempColors.Count > 0)
422 {
423 mruColors = XmlElement.CreateElement("mruColors");
424 foreach (string item in tempColors)
425 {
426 mruColors.AddChildElementWithAttribute("color", "rgb", item);
427 }
428 }
429 return mruColors;
430 }
431 #endregion
432 }
433}