NanoXLSX.Reader 3.1.0
Loading...
Searching...
No Matches
StyleReader.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 © 2026
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
9{
10 using System;
11 using System.Collections.Generic;
12 using System.IO;
13 using System.Xml;
14 using NanoXLSX.Colors;
15 using NanoXLSX.Interfaces;
16 using NanoXLSX.Interfaces.Reader;
17 using NanoXLSX.Registry;
18 using NanoXLSX.Registry.Attributes;
19 using NanoXLSX.Styles;
20 using NanoXLSX.Themes;
21 using NanoXLSX.Utils;
22 using NanoXLSX.Utils.Xml;
23 using static NanoXLSX.Styles.Border;
24 using static NanoXLSX.Styles.CellXf;
25 using static NanoXLSX.Styles.Font;
26 using static NanoXLSX.Styles.NumberFormat;
27 using static NanoXLSX.Themes.Theme;
28 using IOException = Exceptions.IOException;
29
33 [NanoXlsxPlugIn(PlugInUUID = PlugInUUID.StyleReader)]
34 public class StyleReader : IPluginBaseReader
35 {
36
37 private Stream stream;
38 private StyleReaderContainer styleReaderContainer;
39
40 #region properties
44 public Workbook Workbook { get; set; }
48 public IOptions Options { get; set; }
52 public Action<Stream, Workbook, string, IOptions, int?> InlinePluginHandler { get; set; }
53 #endregion
54
55 #region constructors
59 public StyleReader()
60 {
61 }
62 #endregion
63
64 #region methods
65
73 public void Init(Stream stream, Workbook workbook, IOptions readerOptions, Action<Stream, Workbook, string, IOptions, int?> inlinePluginHandler)
74 {
75 this.stream = stream;
76 this.Workbook = workbook;
77 this.Options = readerOptions;
78 this.InlinePluginHandler = inlinePluginHandler;
79 }
80
85 public void Execute()
86 {
87 this.styleReaderContainer = new StyleReaderContainer();
88 try
89 {
90 using (stream) // Close after processing
91 {
92 using (XmlReader reader = XmlReader.Create(stream, XmlStreamUtils.CreateSettings()))
93 {
94 while (reader.Read())
95 {
96 if (reader.NodeType != XmlNodeType.Element)
97 {
98 continue;
99 }
100 switch (reader.LocalName.ToLowerInvariant())
101 {
102 case "numfmts":
103 GetNumberFormats(reader);
104 break;
105 case "borders":
106 GetBorders(reader);
107 break;
108 case "fills":
109 GetFills(reader);
110 break;
111 case "fonts":
112 GetFonts(reader);
113 break;
114 case "colors":
115 GetColors(reader);
116 break;
117 case "cellxfs":
118 GetCellXfs(reader);
119 break;
120 }
121 }
122 HandleMruColors();
123 InlinePluginHandler?.Invoke(stream, Workbook, PlugInUUID.StyleInlineReader, Options, null);
124 }
125 }
126 Workbook.AuxiliaryData.SetData(PlugInUUID.StyleReader, PlugInUUID.StyleEntity, styleReaderContainer);
127 }
128 catch (Exception ex)
129 {
130 throw new IOException("The XML entry could not be read from the input stream. Please see the inner exception:", ex);
131 }
132 }
133
137 private void HandleMruColors()
138 {
139 List<string> mruColors = styleReaderContainer.GetMruColors();
140 foreach (string color in mruColors)
141 {
142 Workbook.AddMruColor(color);
143 }
144 }
145
150 private void GetNumberFormats(XmlReader reader)
151 {
152 using (XmlReader subtree = reader.ReadSubtree())
153 {
154 subtree.Read(); // consume <numFmts>
155 while (subtree.Read())
156 {
157 if (!XmlStreamUtils.IsElement(subtree, "numFmt"))
158 {
159 continue;
160 }
161 NumberFormat numberFormat = new NumberFormat();
162 int id = ParserUtils.ParseInt(subtree.GetAttribute("numFmtId")); // null will rightly throw
163 string code = subtree.GetAttribute("formatCode") ?? string.Empty;
164 numberFormat.CustomFormatID = id;
165 numberFormat.Number = FormatNumber.Custom;
166 numberFormat.InternalID = id;
167 numberFormat.CustomFormatCode = code;
168 this.styleReaderContainer.AddStyleComponent(numberFormat);
169 }
170 }
171 }
172
177 private void GetBorders(XmlReader reader)
178 {
179 using (XmlReader subtree = reader.ReadSubtree())
180 {
181 subtree.Read(); // consume <borders>
182 while (subtree.Read())
183 {
184 if (!XmlStreamUtils.IsElement(subtree, "border"))
185 {
186 continue;
187 }
188 Border borderStyle = new Border();
189 string diagonalDown = subtree.GetAttribute("diagonalDown");
190 if (diagonalDown != null && ParserUtils.ParseBinaryBool(diagonalDown) == 1)
191 {
192 borderStyle.DiagonalDown = true;
193 }
194 string diagonalUp = subtree.GetAttribute("diagonalUp");
195 if (diagonalUp != null && ParserUtils.ParseBinaryBool(diagonalUp) == 1)
196 {
197 borderStyle.DiagonalUp = true;
198 }
199 StyleValue sideStyle;
200 string sideColor;
201 using (XmlReader borderSubtree = subtree.ReadSubtree())
202 {
203 borderSubtree.Read(); // consume <border>
204 while (borderSubtree.Read())
205 {
206 if (borderSubtree.NodeType != XmlNodeType.Element)
207 {
208 continue;
209 }
210 switch (borderSubtree.LocalName.ToLowerInvariant())
211 {
212 case "diagonal":
213 ReadBorderSide(borderSubtree, out sideStyle, out sideColor);
214 borderStyle.DiagonalStyle = sideStyle;
215 borderStyle.DiagonalColor = sideColor;
216 break;
217 case "top":
218 ReadBorderSide(borderSubtree, out sideStyle, out sideColor);
219 borderStyle.TopStyle = sideStyle;
220 borderStyle.TopColor = sideColor;
221 break;
222 case "bottom":
223 ReadBorderSide(borderSubtree, out sideStyle, out sideColor);
224 borderStyle.BottomStyle = sideStyle;
225 borderStyle.BottomColor = sideColor;
226 break;
227 case "left":
228 ReadBorderSide(borderSubtree, out sideStyle, out sideColor);
229 borderStyle.LeftStyle = sideStyle;
230 borderStyle.LeftColor = sideColor;
231 break;
232 case "right":
233 ReadBorderSide(borderSubtree, out sideStyle, out sideColor);
234 borderStyle.RightStyle = sideStyle;
235 borderStyle.RightColor = sideColor;
236 break;
237 }
238 }
239 }
240 borderStyle.InternalID = this.styleReaderContainer.GetNextBorderId();
241 this.styleReaderContainer.AddStyleComponent(borderStyle);
242 }
243 }
244 }
245
250 private static void ReadBorderSide(XmlReader reader, out StyleValue style, out string color)
251 {
252 style = StyleValue.None;
253 color = Border.DefaultBorderColor;
254 string styleAttr = reader.GetAttribute("style");
255 if (styleAttr != null)
256 {
257 style = Border.GetStyleEnum(styleAttr);
258 }
259 if (reader.IsEmptyElement)
260 {
261 return;
262 }
263 using (XmlReader subtree = reader.ReadSubtree())
264 {
265 subtree.Read(); // consume the side element open tag
266 while (subtree.Read())
267 {
268 if (subtree.NodeType == XmlNodeType.Element
269 && subtree.LocalName.Equals("color", StringComparison.OrdinalIgnoreCase))
270 {
271 string rgb = subtree.GetAttribute("rgb");
272 if (rgb != null)
273 {
274 color = rgb;
275 }
276 break;
277 }
278 }
279 }
280 }
281
286 private void GetFills(XmlReader reader)
287 {
288 using (XmlReader subtree = reader.ReadSubtree())
289 {
290 subtree.Read(); // consume <fills>
291 while (subtree.Read())
292 {
293 if (!XmlStreamUtils.IsElement(subtree, "fill"))
294 {
295 continue;
296 }
297 Fill fillStyle = new Fill();
298 using (XmlReader fillSubtree = subtree.ReadSubtree())
299 {
300 fillSubtree.Read(); // consume <fill>
301 while (fillSubtree.Read())
302 {
303 if (!XmlStreamUtils.IsElement(fillSubtree, "patternFill"))
304 {
305 continue;
306 }
307 fillStyle.PatternFill = Fill.GetPatternEnum(fillSubtree.GetAttribute("patternType") ?? string.Empty);
308 using (XmlReader patternSubtree = fillSubtree.ReadSubtree())
309 {
310 patternSubtree.Read(); // consume <patternFill>
311 while (patternSubtree.Read())
312 {
313 if (patternSubtree.NodeType != XmlNodeType.Element)
314 {
315 continue;
316 }
317 if (patternSubtree.LocalName.Equals("fgColor", StringComparison.OrdinalIgnoreCase))
318 {
319 fillStyle.ForegroundColor = ReadColorFromNode(patternSubtree);
320 }
321 else if (patternSubtree.LocalName.Equals("bgColor", StringComparison.OrdinalIgnoreCase))
322 {
323 fillStyle.BackgroundColor = ReadColorFromNode(patternSubtree);
324 }
325 }
326 }
327 }
328 }
329 fillStyle.InternalID = this.styleReaderContainer.GetNextFillId();
330 this.styleReaderContainer.AddStyleComponent(fillStyle);
331 }
332 }
333 }
334
338 private static Color ReadColorFromNode(XmlReader reader)
339 {
340 string autoAttr = reader.GetAttribute("auto");
341 if (!string.IsNullOrEmpty(autoAttr) && ParserUtils.ParseBinaryBool(autoAttr) == 1)
342 {
343 return Color.CreateAuto();
344 }
345 string rgbAttr = reader.GetAttribute("rgb");
346 if (!string.IsNullOrEmpty(rgbAttr))
347 {
348 return Color.CreateRgb(rgbAttr);
349 }
350 string indexedAttr = reader.GetAttribute("indexed");
351 if (!string.IsNullOrEmpty(indexedAttr))
352 {
353 return Color.CreateIndexed(ParserUtils.ParseInt(indexedAttr));
354 }
355 string themeAttr = reader.GetAttribute("theme");
356 if (!string.IsNullOrEmpty(themeAttr))
357 {
358 int themeIndex = ParserUtils.ParseInt(themeAttr);
359 string tintAttr = reader.GetAttribute("tint");
360 double? tint = null;
361 if (!string.IsNullOrEmpty(tintAttr))
362 {
363 tint = ParserUtils.ParseDouble(tintAttr);
364 }
365 return Color.CreateTheme((Theme.ColorSchemeElement)themeIndex, tint);
366 }
367 string systemAttr = reader.GetAttribute("system");
368 if (!string.IsNullOrEmpty(systemAttr))
369 {
370 return Color.CreateSystem(new SystemColor(SystemColor.MapStringToValue(systemAttr)));
371 }
372 return Color.CreateNone();
373 }
374
379 private void GetFonts(XmlReader reader)
380 {
381 using (XmlReader subtree = reader.ReadSubtree())
382 {
383 subtree.Read(); // consume <fonts>
384 while (subtree.Read())
385 {
386 if (!XmlStreamUtils.IsElement(subtree, "font"))
387 {
388 continue;
389 }
390 Font fontStyle = new Font();
391 ReadFontElement(subtree, fontStyle);
392 fontStyle.InternalID = this.styleReaderContainer.GetNextFontId();
393 this.styleReaderContainer.AddStyleComponent(fontStyle);
394 }
395 }
396 }
397
401 private static void ReadFontElement(XmlReader reader, Font fontStyle)
402 {
403 using (XmlReader fontSubtree = reader.ReadSubtree())
404 {
405 fontSubtree.Read(); // consume <font>
406 while (fontSubtree.Read())
407 {
408 if (fontSubtree.NodeType != XmlNodeType.Element)
409 {
410 continue;
411 }
412 string val;
413 switch (fontSubtree.LocalName.ToLowerInvariant())
414 {
415 case "b":
416 fontStyle.Bold = true;
417 break;
418 case "i":
419 fontStyle.Italic = true;
420 break;
421 case "strike":
422 fontStyle.Strike = true;
423 break;
424 case "outline":
425 fontStyle.Outline = true;
426 break;
427 case "shadow":
428 fontStyle.Shadow = true;
429 break;
430 case "condense":
431 fontStyle.Condense = true;
432 break;
433 case "extend":
434 fontStyle.Extend = true;
435 break;
436 case "u":
437 val = fontSubtree.GetAttribute("val");
438 fontStyle.Underline = val == null ? Font.UnderlineValue.Single : Font.GetUnderlineEnum(val);
439 break;
440 case "vertalign":
441 fontStyle.VerticalAlign = Font.GetVerticalTextAlignEnum(fontSubtree.GetAttribute("val"));
442 break;
443 case "sz":
444 fontStyle.Size = ParserUtils.ParseFloat(fontSubtree.GetAttribute("val"));
445 break;
446 case "color":
447 string themeVal = fontSubtree.GetAttribute("theme");
448 if (themeVal != null)
449 {
450 fontStyle.ColorValue = Color.CreateTheme(ParseFontColorSchemeElement(themeVal));
451 }
452 string rgbVal = fontSubtree.GetAttribute("rgb");
453 if (rgbVal != null)
454 {
455 fontStyle.ColorValue = Color.CreateRgb(rgbVal);
456 }
457 break;
458 case "name":
459 fontStyle.Name = fontSubtree.GetAttribute("val");
460 break;
461 case "family":
462 val = fontSubtree.GetAttribute("val");
463 if (val != null)
464 {
465 fontStyle.Family = ParseFontFamilyValue(val);
466 }
467 break;
468 case "scheme":
469 val = fontSubtree.GetAttribute("val");
470 if (val != null)
471 {
472 switch (val)
473 {
474 case "major":
475 fontStyle.Scheme = SchemeValue.Major;
476 break;
477 case "minor":
478 fontStyle.Scheme = SchemeValue.Minor;
479 break;
480 }
481 }
482 break;
483 case "charset":
484 val = fontSubtree.GetAttribute("val");
485 if (val != null)
486 {
487 fontStyle.Charset = ParseFontCharsetValue(val);
488 }
489 break;
490 }
491 }
492 }
493 }
494
498 private static ColorSchemeElement ParseFontColorSchemeElement(string value)
499 {
500 switch (value)
501 {
502 case "1": return ColorSchemeElement.Light1;
503 case "2": return ColorSchemeElement.Dark2;
504 case "3": return ColorSchemeElement.Light2;
505 case "4": return ColorSchemeElement.Accent1;
506 case "5": return ColorSchemeElement.Accent2;
507 case "6": return ColorSchemeElement.Accent3;
508 case "7": return ColorSchemeElement.Accent4;
509 case "8": return ColorSchemeElement.Accent5;
510 case "9": return ColorSchemeElement.Accent6;
511 case "10": return ColorSchemeElement.Hyperlink;
512 case "11": return ColorSchemeElement.FollowedHyperlink;
513 default: return ColorSchemeElement.Dark1;
514 }
515 }
516
520 private static FontFamilyValue ParseFontFamilyValue(string value)
521 {
522 switch (value)
523 {
524 case "1": return FontFamilyValue.Roman;
525 case "2": return FontFamilyValue.Swiss;
526 case "3": return FontFamilyValue.Modern;
527 case "4": return FontFamilyValue.Script;
528 case "5": return FontFamilyValue.Decorative;
529 case "6": return FontFamilyValue.Reserved1;
530 case "7": return FontFamilyValue.Reserved2;
531 case "8": return FontFamilyValue.Reserved3;
532 case "9": return FontFamilyValue.Reserved4;
533 case "10": return FontFamilyValue.Reserved5;
534 case "11": return FontFamilyValue.Reserved6;
535 case "12": return FontFamilyValue.Reserved7;
536 case "13": return FontFamilyValue.Reserved8;
537 case "14": return FontFamilyValue.Reserved9;
538 default: return FontFamilyValue.NotApplicable;
539 }
540 }
541
545 private static CharsetValue ParseFontCharsetValue(string value)
546 {
547 switch (value)
548 {
549 case "0": return CharsetValue.ANSI;
550 case "1": return CharsetValue.Default;
551 case "2": return CharsetValue.Symbols;
552 case "77": return CharsetValue.Macintosh;
553 case "128": return CharsetValue.JIS;
554 case "129": return CharsetValue.Hangul;
555 case "130": return CharsetValue.Johab;
556 case "134": return CharsetValue.GBK;
557 case "136": return CharsetValue.Big5;
558 case "161": return CharsetValue.Greek;
559 case "162": return CharsetValue.Turkish;
560 case "163": return CharsetValue.Vietnamese;
561 case "177": return CharsetValue.Hebrew;
562 case "178": return CharsetValue.Arabic;
563 case "186": return CharsetValue.Baltic;
564 case "204": return CharsetValue.Russian;
565 case "222": return CharsetValue.Thai;
566 case "238": return CharsetValue.EasternEuropean;
567 case "255": return CharsetValue.OEM;
568 default: return CharsetValue.ApplicationDefined;
569 }
570 }
571
577 private void GetCellXfs(XmlReader reader)
578 {
579 using (XmlReader subtree = reader.ReadSubtree())
580 {
581 subtree.Read(); // consume <cellXfs>
582 while (subtree.Read())
583 {
584 if (!XmlStreamUtils.IsElement(subtree, "xf"))
585 {
586 continue;
587 }
588 CellXf cellXfStyle = new CellXf();
589 string applyAlignment = subtree.GetAttribute("applyAlignment");
590 if (applyAlignment != null)
591 {
592 cellXfStyle.ForceApplyAlignment = ParserUtils.ParseBinaryBool(applyAlignment) == 1;
593 }
594 string numFmtIdStr = subtree.GetAttribute("numFmtId");
595 string borderIdStr = subtree.GetAttribute("borderId");
596 string fillIdStr = subtree.GetAttribute("fillId");
597 string fontIdStr = subtree.GetAttribute("fontId");
598 if (!subtree.IsEmptyElement)
599 {
600 using (XmlReader xfSubtree = subtree.ReadSubtree())
601 {
602 xfSubtree.Read(); // consume <xf>
603 while (xfSubtree.Read())
604 {
605 if (xfSubtree.NodeType != XmlNodeType.Element)
606 {
607 continue;
608 }
609 if (xfSubtree.LocalName.Equals("alignment", StringComparison.OrdinalIgnoreCase))
610 {
611 ReadXfAlignment(xfSubtree, cellXfStyle);
612 }
613 else if (xfSubtree.LocalName.Equals("protection", StringComparison.OrdinalIgnoreCase))
614 {
615 ReadXfProtection(xfSubtree, cellXfStyle);
616 }
617 }
618 }
619 }
620 cellXfStyle.InternalID = this.styleReaderContainer.GetNextCellXFId();
621 this.styleReaderContainer.AddStyleComponent(cellXfStyle);
622
623 Style style = new Style();
624 int id;
625 bool hasId;
626
627 hasId = ParserUtils.TryParseInt(numFmtIdStr, out id);
628 NumberFormat format = this.styleReaderContainer.GetNumberFormat(id);
629 if (!hasId || format == null)
630 {
631 FormatNumber formatNumber;
632 NumberFormat.TryParseFormatNumber(id, out formatNumber);
633 format = new NumberFormat
634 {
635 Number = formatNumber,
636 InternalID = id
637 };
638 this.styleReaderContainer.AddStyleComponent(format);
639 }
640 hasId = ParserUtils.TryParseInt(borderIdStr, out id);
641 Border border = this.styleReaderContainer.GetBorder(id);
642 if (!hasId || border == null)
643 {
644 border = new Border
645 {
646 InternalID = this.styleReaderContainer.GetNextBorderId()
647 };
648 }
649 hasId = ParserUtils.TryParseInt(fillIdStr, out id);
650 Fill fill = this.styleReaderContainer.GetFill(id);
651 if (!hasId || fill == null)
652 {
653 fill = new Fill
654 {
655 InternalID = this.styleReaderContainer.GetNextFillId()
656 };
657 }
658 hasId = ParserUtils.TryParseInt(fontIdStr, out id);
659 Font font = this.styleReaderContainer.GetFont(id);
660 if (!hasId || font == null)
661 {
662 font = new Font
663 {
664 InternalID = this.styleReaderContainer.GetNextFontId()
665 };
666 }
667 style.CurrentNumberFormat = format;
668 style.CurrentBorder = border;
669 style.CurrentFill = fill;
670 style.CurrentFont = font;
671 style.CurrentCellXf = cellXfStyle;
672 style.InternalID = this.styleReaderContainer.GetNextStyleId();
673 this.styleReaderContainer.AddStyleComponent(style);
674 }
675 }
676 }
677
681 private static void ReadXfAlignment(XmlReader reader, CellXf cellXfStyle)
682 {
683 string shrinkToFit = reader.GetAttribute("shrinkToFit");
684 if (shrinkToFit != null && ParserUtils.ParseBinaryBool(shrinkToFit) == 1)
685 {
686 cellXfStyle.Alignment = TextBreakValue.ShrinkToFit;
687 }
688 string wrapText = reader.GetAttribute("wrapText");
689 if (wrapText != null && wrapText == "1")
690 {
691 cellXfStyle.Alignment = TextBreakValue.WrapText;
692 }
693 cellXfStyle.HorizontalAlign = CellXf.GetHorizontalAlignEnum(reader.GetAttribute("horizontal") ?? string.Empty);
694 cellXfStyle.VerticalAlign = CellXf.GetVerticalAlignEnum(reader.GetAttribute("vertical") ?? string.Empty);
695 string indent = reader.GetAttribute("indent");
696 if (indent != null)
697 {
698 cellXfStyle.Indent = ParserUtils.ParseInt(indent);
699 }
700 string textRotation = reader.GetAttribute("textRotation");
701 if (textRotation != null)
702 {
703 int rotation = ParserUtils.ParseInt(textRotation);
704 cellXfStyle.TextRotation = rotation > 90 ? 90 - rotation : rotation;
705 }
706 }
707
711 private static void ReadXfProtection(XmlReader reader, CellXf cellXfStyle)
712 {
713 string hidden = reader.GetAttribute("hidden");
714 if (hidden != null && hidden == "1")
715 {
716 cellXfStyle.Hidden = true;
717 }
718 string locked = reader.GetAttribute("locked");
719 if (locked != null && locked == "0")
720 {
721 cellXfStyle.Locked = false;
722 }
723 }
724
729 private void GetColors(XmlReader reader)
730 {
731 using (XmlReader subtree = reader.ReadSubtree())
732 {
733 subtree.Read(); // consume <colors>
734 while (subtree.Read())
735 {
736 if (subtree.NodeType == XmlNodeType.Element
737 && subtree.LocalName.Equals("mruColors", StringComparison.OrdinalIgnoreCase))
738 {
739 ReadMruColors(subtree);
740 }
741 }
742 }
743 }
744
748 private void ReadMruColors(XmlReader reader)
749 {
750 using (XmlReader subtree = reader.ReadSubtree())
751 {
752 subtree.Read(); // consume <mruColors>
753 while (subtree.Read())
754 {
755 if (subtree.NodeType == XmlNodeType.Element
756 && subtree.LocalName.Equals("color", StringComparison.OrdinalIgnoreCase))
757 {
758 string rgb = subtree.GetAttribute("rgb");
759 if (rgb != null)
760 {
761 this.styleReaderContainer.AddMruColor(rgb);
762 }
763 }
764 }
765 }
766 }
767 #endregion
768 }
769}
Action< Stream, Workbook, string, IOptions, int?> InlinePluginHandler
Reference to the ReaderPlugInHandler, to be used for post operations in the Execute method.
StyleReader()
Default constructor - Must be defined for instantiation of the plug-ins.
Workbook Workbook
Workbook reference where read data is stored (should not be null).
void Init(Stream stream, Workbook workbook, IOptions readerOptions, Action< Stream, Workbook, string, IOptions, int?> inlinePluginHandler)
Initialization method (interface implementation).
void Execute()
Method to execute the main logic of the plug-in (interface implementation).
IOptions Options
Reader options.
Class representing a collection of pre-processed styles and their components. This class is internall...
void AddStyleComponent(AbstractStyle component)
Adds a style component and determines the appropriate type of it automatically.
Exceptions.IOException IOException