NanoXLSX.Reader 3.0.0-rc.2
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 © 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
9{
10 using System;
11 using System.IO;
12 using System.Xml;
13 using NanoXLSX.Interfaces.Plugin;
14 using NanoXLSX.Interfaces.Reader;
15 using NanoXLSX.Registry;
16 using NanoXLSX.Registry.Attributes;
17 using NanoXLSX.Styles;
18 using NanoXLSX.Utils;
19 using static NanoXLSX.Styles.Border;
20 using static NanoXLSX.Styles.CellXf;
21 using static NanoXLSX.Styles.Font;
22 using static NanoXLSX.Styles.NumberFormat;
23 using static NanoXLSX.Themes.Theme;
24 using IOException = Exceptions.IOException;
25
29 [NanoXlsxPlugIn(PlugInUUID = PlugInUUID.StyleReader)]
30 public class StyleReader : IPlugInReader
31 {
32
33 private MemoryStream stream;
34 private StyleReaderContainer styleReaderContainer;
35
36 #region properties
40 public Workbook Workbook { get; set; }
41 #endregion
42
43 #region constructors
47 public StyleReader()
48 {
49 }
50 #endregion
51
52 #region methods
56 public void Init(MemoryStream stream, Workbook workbook, IOptions readerOptions)
57 {
58 this.stream = stream;
59 this.Workbook = workbook;
60 }
61
66 public void Execute()
67 {
68 this.styleReaderContainer = new StyleReaderContainer();
69 try
70 {
71 using (stream) // Close after processing
72 {
73 XmlDocument xr = new XmlDocument() { XmlResolver = null };
74 using (XmlReader reader = XmlReader.Create(stream, new XmlReaderSettings() { XmlResolver = null }))
75 {
76 xr.Load(reader);
77 foreach (XmlNode node in xr.DocumentElement.ChildNodes)
78 {
79 if (node.LocalName.Equals("numfmts", StringComparison.OrdinalIgnoreCase)) // Handles custom number formats
80 {
81 GetNumberFormats(node);
82 }
83 else if (node.LocalName.Equals("borders", StringComparison.OrdinalIgnoreCase)) // Handles borders
84 {
85 GetBorders(node);
86 }
87 else if (node.LocalName.Equals("fills", StringComparison.OrdinalIgnoreCase)) // Handles fills
88 {
89 GetFills(node);
90 }
91 else if (node.LocalName.Equals("fonts", StringComparison.OrdinalIgnoreCase)) // Handles fonts
92 {
93 GetFonts(node);
94 }
95 else if (node.LocalName.Equals("colors", StringComparison.OrdinalIgnoreCase)) // Handles MRU colors
96 {
97 GetColors(node);
98 }
99 // TODO: Implement other style components
100 }
101 foreach (XmlNode node in xr.DocumentElement.ChildNodes) // Redo for composition after all style parts are gathered; standard number formats
102 {
103 if (node.LocalName.Equals("cellxfs", StringComparison.OrdinalIgnoreCase))
104 {
105 GetCellXfs(node);
106 }
107 }
108 HandleMruColors();
109 RederPlugInHandler.HandleInlineQueuePlugins(ref stream, Workbook, PlugInUUID.StyleInlineReader);
110 }
111 }
112 Workbook.AuxiliaryData.SetData(PlugInUUID.StyleReader, PlugInUUID.StyleEntity, styleReaderContainer);
113 }
114 catch (Exception ex)
115 {
116 throw new IOException("The XML entry could not be read from the input stream. Please see the inner exception:", ex);
117 }
118 }
119
123 private void HandleMruColors()
124 {
125 if (styleReaderContainer.GetMruColors().Count > 0)
126 {
127 foreach (string color in styleReaderContainer.GetMruColors())
128 {
129 Workbook.AddMruColor(color);
130 }
131 }
132 }
133
138 private void GetNumberFormats(XmlNode node)
139 {
140 foreach (XmlNode childNode in node.ChildNodes)
141 {
142 if (childNode.LocalName.Equals("numfmt", StringComparison.OrdinalIgnoreCase))
143 {
144 NumberFormat numberFormat = new NumberFormat();
145 int id = ParserUtils.ParseInt(ReaderUtils.GetAttribute(childNode, "numFmtId")); // Default will (justified) throw an exception
146 string code = ReaderUtils.GetAttribute(childNode, "formatCode", string.Empty);
147 numberFormat.CustomFormatID = id;
148 numberFormat.Number = FormatNumber.Custom;
149 numberFormat.InternalID = id;
150 numberFormat.CustomFormatCode = code;
151 this.styleReaderContainer.AddStyleComponent(numberFormat);
152 }
153 }
154 }
155
160 private void GetBorders(XmlNode node)
161 {
162 foreach (XmlNode border in node.ChildNodes)
163 {
164 Border borderStyle = new Border();
165 string diagonalDown = ReaderUtils.GetAttribute(border, "diagonalDown");
166 string diagonalUp = ReaderUtils.GetAttribute(border, "diagonalUp");
167 if (diagonalDown != null)
168 {
169 int value = ParserUtils.ParseBinaryBool(diagonalDown);
170 if (value == 1)
171 {
172 borderStyle.DiagonalDown = true;
173 }
174 }
175 if (diagonalUp != null)
176 {
177 int value = ParserUtils.ParseBinaryBool(diagonalUp);
178 if (value == 1)
179 {
180 borderStyle.DiagonalUp = true;
181 }
182 }
183 XmlNode innerNode = ReaderUtils.GetChildNode(border, "diagonal");
184 if (innerNode != null)
185 {
186 borderStyle.DiagonalStyle = ParseBorderStyle(innerNode);
187 borderStyle.DiagonalColor = GetColor(innerNode, Border.DefaultBorderColor);
188 }
189 innerNode = ReaderUtils.GetChildNode(border, "top");
190 if (innerNode != null)
191 {
192 borderStyle.TopStyle = ParseBorderStyle(innerNode);
193 borderStyle.TopColor = GetColor(innerNode, Border.DefaultBorderColor);
194 }
195 innerNode = ReaderUtils.GetChildNode(border, "bottom");
196 if (innerNode != null)
197 {
198 borderStyle.BottomStyle = ParseBorderStyle(innerNode);
199 borderStyle.BottomColor = GetColor(innerNode, Border.DefaultBorderColor);
200 }
201 innerNode = ReaderUtils.GetChildNode(border, "left");
202 if (innerNode != null)
203 {
204 borderStyle.LeftStyle = ParseBorderStyle(innerNode);
205 borderStyle.LeftColor = GetColor(innerNode, Border.DefaultBorderColor);
206 }
207 innerNode = ReaderUtils.GetChildNode(border, "right");
208 if (innerNode != null)
209 {
210 borderStyle.RightStyle = ParseBorderStyle(innerNode);
211 borderStyle.RightColor = GetColor(innerNode, Border.DefaultBorderColor);
212 }
213 borderStyle.InternalID = this.styleReaderContainer.GetNextBorderId();
214 this.styleReaderContainer.AddStyleComponent(borderStyle);
215 }
216 }
217
223 private static StyleValue ParseBorderStyle(XmlNode innerNode)
224 {
225 string value = ReaderUtils.GetAttribute(innerNode, "style");
226 if (value != null)
227 {
228 return Border.GetStyleEnum(value);
229 }
230 return StyleValue.None;
231 }
232
237 private void GetFills(XmlNode node)
238 {
239 string attribute;
240 foreach (XmlNode fill in node.ChildNodes)
241 {
242 Fill fillStyle = new Fill();
243 XmlNode innerNode = ReaderUtils.GetChildNode(fill, "patternFill");
244 if (innerNode != null)
245 {
246 string pattern = ReaderUtils.GetAttribute(innerNode, "patternType", string.Empty);
247 fillStyle.PatternFill = Fill.GetPatternEnum(pattern);
248 if (ReaderUtils.GetAttributeOfChild(innerNode, "fgColor", "rgb", out attribute))
249 {
250 if (!string.IsNullOrEmpty(attribute))
251 {
252 fillStyle.ForegroundColor = attribute;
253 }
254 }
255 XmlNode backgroundNode = ReaderUtils.GetChildNode(innerNode, "bgColor");
256 if (backgroundNode != null)
257 {
258 string backgroundArgb = ReaderUtils.GetAttribute(backgroundNode, "rgb");
259 if (!string.IsNullOrEmpty(backgroundArgb))
260 {
261 fillStyle.BackgroundColor = backgroundArgb;
262 }
263 string backgroundIndex = ReaderUtils.GetAttribute(backgroundNode, "indexed");
264 if (!string.IsNullOrEmpty(backgroundIndex))
265 {
266 fillStyle.IndexedColor = ParserUtils.ParseInt(backgroundIndex);
267 }
268 }
269 }
270
271 fillStyle.InternalID = this.styleReaderContainer.GetNextFillId();
272 this.styleReaderContainer.AddStyleComponent(fillStyle);
273 }
274 }
275
280 private void GetFonts(XmlNode node)
281 {
282 string attribute;
283 foreach (XmlNode font in node.ChildNodes)
284 {
285 Font fontStyle = new Font();
286 XmlNode boldNode = ReaderUtils.GetChildNode(font, "b");
287 if (boldNode != null)
288 {
289 fontStyle.Bold = true;
290 }
291 XmlNode italicdNode = ReaderUtils.GetChildNode(font, "i");
292 if (italicdNode != null)
293 {
294 fontStyle.Italic = true;
295 }
296 XmlNode strikeNode = ReaderUtils.GetChildNode(font, "strike");
297 if (strikeNode != null)
298 {
299 fontStyle.Strike = true;
300 }
301 if (ReaderUtils.GetAttributeOfChild(font, "u", "val", out attribute))
302 {
303 if (attribute == null)
304 {
305 fontStyle.Underline = Font.UnderlineValue.Single; // Default value
306 }
307 else
308 {
309 fontStyle.Underline = Font.GetUnderlineEnum(attribute);
310 }
311 }
312 if (ReaderUtils.GetAttributeOfChild(font, "vertAlign", "val", out attribute))
313 {
314 fontStyle.VerticalAlign = Font.GetVerticalTextAlignEnum(attribute);
315 }
316 if (ReaderUtils.GetAttributeOfChild(font, "sz", "val", out attribute))
317 {
318 fontStyle.Size = ParserUtils.ParseFloat(attribute);
319 }
320 XmlNode colorNode = ReaderUtils.GetChildNode(font, "color");
321 if (colorNode != null)
322 {
323
324 attribute = ReaderUtils.GetAttribute(colorNode, "theme");
325 if (attribute != null)
326 {
327 switch (attribute)
328 {
329 case "0":
330 fontStyle.ColorTheme = ColorSchemeElement.Dark1;
331 break;
332 case "1":
333 fontStyle.ColorTheme = ColorSchemeElement.Light1;
334 break;
335 case "2":
336 fontStyle.ColorTheme = ColorSchemeElement.Dark2;
337 break;
338 case "3":
339 fontStyle.ColorTheme = ColorSchemeElement.Light2;
340 break;
341 case "4":
342 fontStyle.ColorTheme = ColorSchemeElement.Accent1;
343 break;
344 case "5":
345 fontStyle.ColorTheme = ColorSchemeElement.Accent2;
346 break;
347 case "6":
348 fontStyle.ColorTheme = ColorSchemeElement.Accent3;
349 break;
350 case "7":
351 fontStyle.ColorTheme = ColorSchemeElement.Accent4;
352 break;
353 case "8":
354 fontStyle.ColorTheme = ColorSchemeElement.Accent5;
355 break;
356 case "9":
357 fontStyle.ColorTheme = ColorSchemeElement.Accent6;
358 break;
359 case "10":
360 fontStyle.ColorTheme = ColorSchemeElement.Hyperlink;
361 break;
362 case "11":
363 fontStyle.ColorTheme = ColorSchemeElement.FollowedHyperlink;
364 break;
365 }
366 }
367 attribute = ReaderUtils.GetAttribute(colorNode, "rgb");
368 if (attribute != null)
369 {
370 fontStyle.ColorValue = attribute;
371 }
372 }
373 if (ReaderUtils.GetAttributeOfChild(font, "name", "val", out attribute))
374 {
375 fontStyle.Name = attribute;
376 }
377 if (ReaderUtils.GetAttributeOfChild(font, "family", "val", out attribute))
378 {
379 switch (attribute)
380 {
381
382 case "0":
383 fontStyle.Family = FontFamilyValue.NotApplicable;
384 break;
385 case "1":
386 fontStyle.Family = FontFamilyValue.Roman;
387 break;
388 case "2":
389 fontStyle.Family = FontFamilyValue.Swiss;
390 break;
391 case "3":
392 fontStyle.Family = FontFamilyValue.Modern;
393 break;
394 case "4":
395 fontStyle.Family = FontFamilyValue.Script;
396 break;
397 case "5":
398 fontStyle.Family = FontFamilyValue.Decorative;
399 break;
400 case "6":
401 fontStyle.Family = FontFamilyValue.Reserved1;
402 break;
403 case "7":
404 fontStyle.Family = FontFamilyValue.Reserved2;
405 break;
406 case "8":
407 fontStyle.Family = FontFamilyValue.Reserved3;
408 break;
409 case "9":
410 fontStyle.Family = FontFamilyValue.Reserved4;
411 break;
412 case "10":
413 fontStyle.Family = FontFamilyValue.Reserved5;
414 break;
415 case "11":
416 fontStyle.Family = FontFamilyValue.Reserved6;
417 break;
418 case "12":
419 fontStyle.Family = FontFamilyValue.Reserved7;
420 break;
421 case "13":
422 fontStyle.Family = FontFamilyValue.Reserved8;
423 break;
424 case "14":
425 fontStyle.Family = FontFamilyValue.Reserved9;
426 break;
427 }
428 }
429 if (ReaderUtils.GetAttributeOfChild(font, "scheme", "val", out attribute))
430 {
431 switch (attribute)
432 {
433 case "major":
434 fontStyle.Scheme = SchemeValue.Major;
435 break;
436 case "minor":
437 fontStyle.Scheme = SchemeValue.Minor;
438 break;
439 }
440 }
441 if (ReaderUtils.GetAttributeOfChild(font, "charset", "val", out attribute))
442 {
443 switch (attribute)
444 {
445 case "0":
446 fontStyle.Charset = CharsetValue.ANSI;
447 break;
448 case "1":
449 fontStyle.Charset = CharsetValue.Default;
450 break;
451 case "2":
452 fontStyle.Charset = CharsetValue.Symbols;
453 break;
454 case "77":
455 fontStyle.Charset = CharsetValue.Macintosh;
456 break;
457 case "128":
458 fontStyle.Charset = CharsetValue.JIS;
459 break;
460 case "129":
461 fontStyle.Charset = CharsetValue.Hangul;
462 break;
463 case "130":
464 fontStyle.Charset = CharsetValue.Johab;
465 break;
466 case "134":
467 fontStyle.Charset = CharsetValue.GBK;
468 break;
469 case "136":
470 fontStyle.Charset = CharsetValue.Big5;
471 break;
472 case "161":
473 fontStyle.Charset = CharsetValue.Greek;
474 break;
475 case "162":
476 fontStyle.Charset = CharsetValue.Turkish;
477 break;
478 case "163":
479 fontStyle.Charset = CharsetValue.Vietnamese;
480 break;
481 case "177":
482 fontStyle.Charset = CharsetValue.Hebrew;
483 break;
484 case "178":
485 fontStyle.Charset = CharsetValue.Arabic;
486 break;
487 case "186":
488 fontStyle.Charset = CharsetValue.Baltic;
489 break;
490 case "204":
491 fontStyle.Charset = CharsetValue.Russian;
492 break;
493 case "222":
494 fontStyle.Charset = CharsetValue.Thai;
495 break;
496 case "238":
497 fontStyle.Charset = CharsetValue.EasternEuropean;
498 break;
499 case "255":
500 fontStyle.Charset = CharsetValue.OEM;
501 break;
502 default:
503 fontStyle.Charset = CharsetValue.ApplicationDefined;
504 break;
505 }
506 }
507
508 fontStyle.InternalID = this.styleReaderContainer.GetNextFontId();
509 this.styleReaderContainer.AddStyleComponent(fontStyle);
510 }
511 }
512
517 private void GetCellXfs(XmlNode node)
518 {
519 foreach (XmlNode childNode in node.ChildNodes)
520 {
521 if (ReaderUtils.IsNode(childNode, "xf"))
522 {
523 CellXf cellXfStyle = new CellXf();
524 string attribute = ReaderUtils.GetAttribute(childNode, "applyAlignment");
525 if (attribute != null)
526 {
527 int value = ParserUtils.ParseBinaryBool(attribute);
528 cellXfStyle.ForceApplyAlignment = value == 1;
529 }
530 XmlNode alignmentNode = ReaderUtils.GetChildNode(childNode, "alignment");
531 if (alignmentNode != null)
532 {
533 attribute = ReaderUtils.GetAttribute(alignmentNode, "shrinkToFit");
534 if (attribute != null)
535 {
536 int value = ParserUtils.ParseBinaryBool(attribute);
537 if (value == 1)
538 {
539 cellXfStyle.Alignment = TextBreakValue.ShrinkToFit;
540 }
541 }
542 attribute = ReaderUtils.GetAttribute(alignmentNode, "wrapText");
543 if (attribute != null && attribute == "1")
544 {
545 cellXfStyle.Alignment = TextBreakValue.WrapText;
546 }
547 attribute = ReaderUtils.GetAttribute(alignmentNode, "horizontal", string.Empty);
548 cellXfStyle.HorizontalAlign = CellXf.GetHorizontalAlignEnum(attribute);
549 attribute = ReaderUtils.GetAttribute(alignmentNode, "vertical", string.Empty);
550 cellXfStyle.VerticalAlign = CellXf.GetVerticalAlignEnum(attribute);
551 attribute = ReaderUtils.GetAttribute(alignmentNode, "indent");
552 if (attribute != null)
553 {
554 cellXfStyle.Indent = ParserUtils.ParseInt(attribute);
555 }
556 attribute = ReaderUtils.GetAttribute(alignmentNode, "textRotation");
557 if (attribute != null)
558 {
559 int rotation = ParserUtils.ParseInt(attribute);
560 cellXfStyle.TextRotation = rotation > 90 ? 90 - rotation : rotation;
561 }
562 }
563 XmlNode protectionNode = ReaderUtils.GetChildNode(childNode, "protection");
564 if (protectionNode != null)
565 {
566 attribute = ReaderUtils.GetAttribute(protectionNode, "hidden");
567 if (attribute != null && attribute == "1")
568 {
569 cellXfStyle.Hidden = true;
570 }
571 attribute = ReaderUtils.GetAttribute(protectionNode, "locked");
572 if (attribute != null && attribute == "0")
573 {
574 cellXfStyle.Locked = false;
575 }
576 // else - NoOp - No need to set locked value, since true by default
577 }
578
579 cellXfStyle.InternalID = this.styleReaderContainer.GetNextCellXFId();
580 this.styleReaderContainer.AddStyleComponent(cellXfStyle);
581
582 Style style = new Style();
583 int id;
584 bool hasId;
585
586 hasId = ParserUtils.TryParseInt(ReaderUtils.GetAttribute(childNode, "numFmtId"), out id);
587 NumberFormat format = this.styleReaderContainer.GetNumberFormat(id);
588 if (!hasId || format == null)
589 {
590 FormatNumber formatNumber;
591 NumberFormat.TryParseFormatNumber(id, out formatNumber); // Validity is neglected here to prevent unhandled crashes. If invalid, the format will be declared as 'none'.
592 // Invalid values should not occur at all (malformed Excel files).
593 // Undefined values may occur if the file was saved by an Excel version that has implemented yet unknown format numbers (undefined in NanoXLSX)
594 format = new NumberFormat
595 {
596 Number = formatNumber,
597 InternalID = id
598 };
599 this.styleReaderContainer.AddStyleComponent(format);
600 }
601 hasId = ParserUtils.TryParseInt(ReaderUtils.GetAttribute(childNode, "borderId"), out id);
602 Border border = this.styleReaderContainer.GetBorder(id);
603 if (!hasId || border == null)
604 {
605 border = new Border
606 {
607 InternalID = this.styleReaderContainer.GetNextBorderId()
608 };
609 }
610 hasId = ParserUtils.TryParseInt(ReaderUtils.GetAttribute(childNode, "fillId"), out id);
611 Fill fill = this.styleReaderContainer.GetFill(id);
612 if (!hasId || fill == null)
613 {
614 fill = new Fill
615 {
616 InternalID = this.styleReaderContainer.GetNextFillId()
617 };
618 }
619 hasId = ParserUtils.TryParseInt(ReaderUtils.GetAttribute(childNode, "fontId"), out id);
620 Font font = this.styleReaderContainer.GetFont(id);
621 if (!hasId || font == null)
622 {
623 font = new Font
624 {
625 InternalID = this.styleReaderContainer.GetNextFontId()
626 };
627 }
628
629 // TODO: Implement other style information
630 style.CurrentNumberFormat = format;
631 style.CurrentBorder = border;
632 style.CurrentFill = fill;
633 style.CurrentFont = font;
634 style.CurrentCellXf = cellXfStyle;
635 style.InternalID = this.styleReaderContainer.GetNextStyleId();
636
637 this.styleReaderContainer.AddStyleComponent(style);
638 }
639 }
640 }
641
646 private void GetColors(XmlNode node)
647 {
648 foreach (XmlNode color in node.ChildNodes)
649 {
650 XmlNode mruColor = ReaderUtils.GetChildNode(color, "color");
651 if (color.Name.Equals("mruColors", StringComparison.Ordinal) && mruColor != null)
652 {
653 foreach (XmlNode value in color.ChildNodes)
654 {
655 string attribute = ReaderUtils.GetAttribute(value, "rgb");
656 if (attribute != null)
657 {
658 this.styleReaderContainer.AddMruColor(attribute);
659 }
660 }
661 }
662 }
663 }
664
671 private static string GetColor(XmlNode node, string fallback)
672 {
673 XmlNode childNode = ReaderUtils.GetChildNode(node, "color");
674 if (childNode != null)
675 {
676 string color = ReaderUtils.GetAttribute(childNode, "rgb");
677 if (color != null)
678 {
679 return color;
680 }
681 }
682 return fallback;
683 }
684 #endregion
685 }
686}
StyleReader()
Initializes a new instance of the StyleReader class.
Workbook Workbook
Workbook reference where read data is stored (should not be null).
void Execute()
Method to execute the main logic of the plug-in (interface implementation).
void Init(MemoryStream stream, Workbook workbook, IOptions readerOptions)
Default constructor - Must be defined for instantiation of the plug-ins.
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