NanoXLSX.Core 3.2.1
Loading...
Searching...
No Matches
ParserUtils.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
8using System;
9using System.Globalization;
10using System.Linq;
11using System.Text;
12
13namespace NanoXLSX.Utils
14{
19 public static class ParserUtils
20 {
21 #region constants
22
26 public const string NumericFormat = "G";
27
34 public static readonly CultureInfo InvariantCulture = CultureInfo.InvariantCulture;
35
36 #endregion
37
38 #region primitiveParsing
45 public static bool StartsWith(string input, string value)
46 {
47 if (input == null && value == null)
48 {
49 return true;
50 }
51 else if (input == null && value != null)
52 {
53 return false;
54 }
55 else if (value == null)
56 {
57 return false;
58 }
59 return input.StartsWith(value, StringComparison.InvariantCulture);
60 }
61
68 public static bool NotStartsWith(string input, string value)
69 {
70 return !StartsWith(input, value);
71 }
72
78 public static string ToUpper(string input)
79 {
80 return !string.IsNullOrEmpty(input) ? input.ToUpper(InvariantCulture) : input;
81 }
82
88 public static string ToLower(string input)
89 {
90 return !string.IsNullOrEmpty(input) ? input.ToLower(InvariantCulture) : input;
91 }
92
98 public static string ToString(int input)
99 {
100 return input.ToString(NumericFormat, InvariantCulture);
101 }
102
108 public static string ToString(float input)
109 {
110 return input.ToString(NumericFormat, InvariantCulture);
111 }
112
118 public static string ToString(byte input)
119 {
120 return input.ToString(NumericFormat, InvariantCulture);
121 }
122
128 public static string ToString(sbyte input)
129 {
130 return input.ToString(NumericFormat, InvariantCulture);
131 }
132
138 public static string ToString(double input)
139 {
140 return input.ToString(NumericFormat, InvariantCulture);
141 }
142
148 public static string ToString(decimal input)
149 {
150 return input.ToString(NumericFormat, InvariantCulture);
151 }
152
158 public static string ToString(uint input)
159 {
160 return input.ToString(NumericFormat, InvariantCulture);
161 }
162
168 public static string ToString(long input)
169 {
170 return input.ToString(NumericFormat, InvariantCulture);
171 }
172
178 public static string ToString(ulong input)
179 {
180 return input.ToString(NumericFormat, InvariantCulture);
181 }
182
188 public static string ToString(short input)
189 {
190 return input.ToString(NumericFormat, InvariantCulture);
191 }
192
198 public static string ToString(ushort input)
199 {
200 return input.ToString(NumericFormat, InvariantCulture);
201 }
202
209 public static float ParseFloat(string rawValue)
210 {
211 return float.Parse(rawValue, InvariantCulture);
212 }
213
220 public static int ParseInt(string rawValue)
221 {
222 return int.Parse(rawValue, NumberStyles.Any, InvariantCulture);
223 }
224
231 public static double ParseDouble(string rawValue)
232 {
233 return double.Parse(rawValue, InvariantCulture);
234 }
235
241 public static int ParseBinaryBool(String rawValue)
242 {
243 if (string.IsNullOrEmpty(rawValue))
244 {
245 return 0;
246 }
247 int value;
248 if (TryParseInt(rawValue, out value))
249 {
250 return value >= 1 ? 1 : 0;
251 }
252 bool regularBool;
253 if (TryParseBool(rawValue, out regularBool))
254 {
255 return regularBool ? 1 : 0;
256 }
257 return 0;
258 }
259
267 public static bool TryParseBool(string rawValue, out bool parsedValue)
268 {
269 return bool.TryParse(rawValue, out parsedValue);
270 }
271
278 public static bool TryParseInt(string rawValue, out int parsedValue)
279 {
280 return int.TryParse(rawValue, NumberStyles.Integer, InvariantCulture, out parsedValue);
281 }
282
289 public static bool TryParseUint(string rawValue, out uint parsedValue)
290 {
291 return uint.TryParse(rawValue, NumberStyles.Integer, InvariantCulture, out parsedValue);
292 }
293
300 public static bool TryParseLong(string rawValue, out long parsedValue)
301 {
302 return long.TryParse(rawValue, NumberStyles.Integer, InvariantCulture, out parsedValue);
303 }
304
311 public static bool TryParseUlong(string rawValue, out ulong parsedValue)
312 {
313 return ulong.TryParse(rawValue, NumberStyles.Integer, InvariantCulture, out parsedValue);
314 }
315
322 public static bool TryParseFloat(string rawValue, out float parsedValue)
323 {
324 return float.TryParse(rawValue, NumberStyles.Any, CultureInfo.InvariantCulture, out parsedValue);
325 }
326
333 public static bool TryParseDecimal(string rawValue, out decimal parsedValue)
334 {
335 return decimal.TryParse(rawValue, NumberStyles.Float, InvariantCulture, out parsedValue);
336 }
337
345 public static bool TryParseDouble(string rawValue, out double parsedValue, NumberStyles numberStyles = NumberStyles.Any)
346 {
347 return double.TryParse(rawValue, numberStyles, InvariantCulture, out parsedValue);
348 }
349
355 public static string NormalizeNewLines(string value)
356 {
357 if (value == null || (!value.Contains('\n') && !value.Contains('\r')))
358 {
359 return value;
360 }
361 return value.Replace("\n\r", "\n").Replace("\r\n", "\n").Replace("\r", "\n").Replace("\n", "\r\n");
362 }
363
369 public static bool IsAsciiDigit(char character)
370 {
371 return character >= '0' && character <= '9';
372 }
373 #endregion
374
375 #region formulaParsing
388 public static string ToCachedValueString(object input, bool convertBoolToNumber = true)
389 {
390 if (input == null) { return "0"; }
391 else if (input is string)
392 {
393 string stringValue = input as string;
394 return string.IsNullOrEmpty(stringValue) ? "0" : stringValue;
395 }
396 else if (input is bool)
397 {
398 if (convertBoolToNumber)
399 {
400 return (bool)input ? "1" : "0";
401 }
402 else
403 {
404 return (bool)input ? "TRUE" : "FALSE";
405 }
406 }
407 else if (input is byte) { return ToString((byte)input); }
408 else if (input is sbyte) { return ToString((sbyte)input); }
409 else if (input is decimal) { return ToString((decimal)input); }
410 else if (input is double) { return ToString((double)input); }
411 else if (input is float) { return ToString((float)input); }
412 else if (input is int) { return ToString((int)input); }
413 else if (input is uint) { return ToString((uint)input); }
414 else if (input is long) { return ToString((long)input); }
415 else if (input is ulong) { return ToString((ulong)input); }
416 else if (input is short) { return ToString((short)input); }
417 else if (input is ushort) { return ToString((ushort)input); }
418 else if (input is DateTime)
419 {
420 return DataUtils.GetOADateTimeString((DateTime)input);
421 }
422 else if (input is TimeSpan)
423 {
424 return DataUtils.GetOATimeString((TimeSpan)input);
425 }
426 else
427 {
428 return input.ToString(); // Generic string
429 }
430 }
431
440 public static bool TryParseFormulaStringConstant(string expression, out string value, bool enclosingQuotesRemoved = false)
441 {
442 value = null;
443 if (expression == null)
444 {
445 return false;
446 }
447
448 int startIndex;
449 int endIndex;
450 if (enclosingQuotesRemoved)
451 {
452 // The complete expression represents the content inside the string literal.
453 startIndex = 0;
454 endIndex = expression.Length;
455 }
456 else
457 {
458 // A complete formula string constant requires enclosing double quotes.
459 if (expression.Length < 2
460 || expression[0] != '"'
461 || expression[expression.Length - 1] != '"')
462 {
463 return false;
464 }
465 startIndex = 1;
466 endIndex = expression.Length - 1;
467 }
468
469 StringBuilder builder = new StringBuilder(endIndex - startIndex);
470 for (int i = startIndex; i < endIndex; i++)
471 {
472 char current = expression[i];
473 if (current != '"')
474 {
475 builder.Append(current);
476 continue;
477 }
478 // Quotes inside an Excel string constant must always occur as a pair.
479 if (i + 1 >= endIndex || expression[i + 1] != '"')
480 {
481 return false;
482 }
483 builder.Append('"');
484 i++;
485 }
486
487 value = builder.ToString();
488 return true;
489 }
490 #endregion
491
492 #region referenceParsing
501 public static bool TryParseWorksheetQualifiedReference(string expression, out string worksheetName, out string reference)
502 {
503 worksheetName = null;
504 reference = null;
505
506 if (string.IsNullOrEmpty(expression))
507 {
508 return false;
509 }
510
511 if (expression[0] != '\'')
512 {
513 int separatorIndex = expression.IndexOf('!');
514 if (separatorIndex <= 0 || separatorIndex == expression.Length - 1)
515 {
516 return false;
517 }
518
519 worksheetName = expression.Substring(0, separatorIndex);
520 reference = expression.Substring(separatorIndex + 1);
521 return true;
522 }
523
524 StringBuilder builder = new StringBuilder();
525 for (int i = 1; i < expression.Length; i++)
526 {
527 char current = expression[i];
528 if (current != '\'')
529 {
530 builder.Append(current);
531 continue;
532 }
533
534 // Two apostrophes inside a quoted worksheet name represent one literal apostrophe.
535 if (i + 1 < expression.Length && expression[i + 1] == '\'')
536 {
537 builder.Append('\'');
538 i++;
539 continue;
540 }
541
542 // A single apostrophe closes the quoted worksheet name. It must immediately be followed by the reference separator.
543 if (i + 1 >= expression.Length || expression[i + 1] != '!')
544 {
545 return false;
546 }
547
548 if (i + 2 >= expression.Length)
549 {
550 return false;
551 }
552
553 worksheetName = builder.ToString();
554 reference = expression.Substring(i + 2);
555 return true;
556 }
557 // No closing apostrophe was found.
558 return false;
559 }
560 #endregion
561
562 #region externalReferenceParsing
563
570 internal static bool ContainsExternalReference(string formulaExpression)
571 {
572 if (string.IsNullOrEmpty(formulaExpression) || formulaExpression.IndexOf('[') < 0)
573 {
574 return false;
575 }
576
577 bool inStringLiteral = false;
578 for (int i = 0; i < formulaExpression.Length; i++)
579 {
580 char current = formulaExpression[i];
581 if (current == '"')
582 {
583 if (inStringLiteral && i + 1 < formulaExpression.Length && formulaExpression[i + 1] == '"')
584 {
585 i++;
586 continue;
587 }
588 inStringLiteral = !inStringLiteral;
589 continue;
590 }
591 if (inStringLiteral || current != '[')
592 {
593 continue;
594 }
595
596 int closingBracket = formulaExpression.IndexOf(']', i + 1);
597 if (closingBracket <= i + 1)
598 {
599 continue;
600 }
601
602 bool hasWorksheetName = false;
603 for (int j = closingBracket + 1; j < formulaExpression.Length; j++)
604 {
605 char referenceCharacter = formulaExpression[j];
606 if (referenceCharacter == '!')
607 {
608 if (hasWorksheetName)
609 {
610 return true;
611 }
612 break;
613 }
614 if (referenceCharacter == '[' || referenceCharacter == ']' || referenceCharacter == '"'
615 || referenceCharacter == '+' || referenceCharacter == '-' || referenceCharacter == '*'
616 || referenceCharacter == '/' || referenceCharacter == '^' || referenceCharacter == '&'
617 || referenceCharacter == '=' || referenceCharacter == '<' || referenceCharacter == '>'
618 || referenceCharacter == ',' || referenceCharacter == ';' || referenceCharacter == '('
619 || referenceCharacter == ')' || referenceCharacter == '{' || referenceCharacter == '}')
620 {
621 break;
622 }
623 if (!char.IsWhiteSpace(referenceCharacter) && referenceCharacter != '\'')
624 {
625 hasWorksheetName = true;
626 }
627 }
628 i = closingBracket;
629 }
630 return false;
631 }
632
638 internal static bool IsValidExternalLinkId(string identifier)
639 {
640 if (string.IsNullOrEmpty(identifier) ||
641 identifier.Length < 3 ||
642 identifier[0] != '[' ||
643 identifier[identifier.Length - 1] != ']')
644 {
645 return false;
646 }
647
648 for (int i = 1; i < identifier.Length - 1; i++)
649 {
650 if (!IsAsciiDigit(identifier[i]))
651 {
652 return false;
653 }
654 }
655 return true;
656 }
657
667 internal static bool TryReadExternalLinkId(string expression, int startIndex, out int identifierLength)
668 {
669 identifierLength = 0;
670 if (string.IsNullOrEmpty(expression) ||
671 startIndex < 0 || startIndex >= expression.Length || expression[startIndex] != '[')
672 {
673 return false;
674 }
675
676 int currentIndex = startIndex + 1;
677 if (currentIndex >= expression.Length ||
678 !IsAsciiDigit(expression[currentIndex]))
679 {
680 return false;
681 }
682
683 do
684 {
685 currentIndex++;
686 }
687 while (currentIndex < expression.Length && IsAsciiDigit(expression[currentIndex]));
688 if (currentIndex >= expression.Length || expression[currentIndex] != ']')
689 {
690 return false;
691 }
692
693 int closingBracketIndex = currentIndex;
694 if (!HasValidPrefixBoundary(expression, startIndex))
695 {
696 return false;
697 }
698 if (!HasValidSuffixBoundary(expression, closingBracketIndex))
699 {
700 return false;
701 }
702 identifierLength = closingBracketIndex - startIndex + 1;
703 return true;
704 }
705
709 private static bool HasValidPrefixBoundary(string expression, int openingBracketIndex)
710 {
711 if (openingBracketIndex == 0)
712 {
713 return true;
714 }
715
716 char previous = expression[openingBracketIndex - 1];
717 // Quoted external sheet reference: '[1]Sheet name'!A1
718 if (previous == '\'')
719 {
720 return true;
721 }
722 // Table1[1], SomeName[2], etc.
723 return !IsNameCharacter(previous);
724 }
725
729 private static bool HasValidSuffixBoundary(string expression, int closingBracketIndex)
730 {
731 int nextIndex = closingBracketIndex + 1;
732 if (nextIndex >= expression.Length)
733 {
734 // A bare [1] can be a structured table-column reference.
735 return false;
736 }
737 char next = expression[nextIndex];
738 // External defined name / workbook prefix: [1]!ExternalName
739 if (next == '!')
740 {
741 return true;
742 }
743 // Broken external sheet reference: [1]#REF!A1
744 if (next == '#')
745 {
746 return true;
747 }
748 // The sheet or external name must immediately follow the ID.
749 if (char.IsWhiteSpace(next))
750 {
751 return false;
752 }
753 switch (next)
754 {
755 case '"':
756 case '[':
757 case ']':
758 case '(':
759 case ')':
760 case ',':
761 case ';':
762 case '+':
763 case '-':
764 case '*':
765 case '/':
766 case '^':
767 case '&':
768 case '=':
769 case '<':
770 case '>':
771 case '%':
772 case ':':
773 return false;
774 default:
775 return true;
776 }
777 }
778
784 private static bool IsNameCharacter(char character)
785 {
786 return char.IsLetterOrDigit(character) ||
787 character == '_' ||
788 character == '\\' ||
789 character == '.';
790 }
791
792 #endregion
793
794 }
795}
General data utils class with static methods.
Definition DataUtils.cs:21
static string GetOATimeString(TimeSpan time)
Method to convert a time into the internal Excel time format (OAdate without days).
Definition DataUtils.cs:147
static string GetOADateTimeString(DateTime date)
Method to convert a date or date and time into the internal Excel time format (OAdate).
Definition DataUtils.cs:105
Class providing static methods to parse string values to specific types or to print object as languag...
static string ToString(long input)
Transforms a long to an invariant sting.
const string NumericFormat
Numeric format for ToString conversions. This format ensures that a numeric value is printed in a lan...
static string ToString(byte input)
Transforms a byte to an invariant sting.
static readonly CultureInfo InvariantCulture
Constant for number conversion. The invariant culture (represents mostly the US numbering scheme) ens...
static bool TryParseDecimal(string rawValue, out decimal parsedValue)
Tries to parse a decimal (with float parsing style) independent from the culture info of the host.
static string ToString(double input)
Transforms a double to an invariant sting.
static bool TryParseFloat(string rawValue, out float parsedValue)
Tries to parse a float (with any parsing style) independent from the culture info of the host.
static string ToString(ulong input)
Transforms a ulong to an invariant sting.
static bool StartsWith(string input, string value)
Determines whether a string starts with a specific value.
static string ToUpper(string input)
Transforms a string to upper case with null check and invariant culture.
static string ToString(short input)
Transforms a short to an invariant sting.
static string ToString(sbyte input)
Transforms a sbyte to an invariant sting.
static string ToString(decimal input)
Transforms a decimal to an invariant sting.
static double ParseDouble(string rawValue)
Parses a double independent from the culture info of the host.
static string ToCachedValueString(object input, bool convertBoolToNumber=true)
Transforms a given object to a string displayed as cached Values. The common known compatible numeric...
static string ToLower(string input)
Transforms a string to lower case with null check and invariant culture.
static bool TryParseInt(string rawValue, out int parsedValue)
Tries to parse an int independent of the culture info of the host.
static bool TryParseLong(string rawValue, out long parsedValue)
Tries to parse a long independent from the culture info of the host.
static bool TryParseUlong(string rawValue, out ulong parsedValue)
Tries to parse an unsigned long (ulong) independent from the culture info of the host.
static string ToString(uint input)
Transforms a uint to an invariant sting.
static string ToString(ushort input)
Transforms a ushort to an invariant sting.
static float ParseFloat(string rawValue)
Parses a float independent from the culture info of the host.
static bool NotStartsWith(string input, string value)
Determines whether a string does not start with a specific value.
static int ParseInt(string rawValue)
Parses an int independent from the culture info of the host.
static bool TryParseUint(string rawValue, out uint parsedValue)
Tries to parse an unsigned int (uint) independent from the culture info of the host.
static bool TryParseBool(string rawValue, out bool parsedValue)
Tries to parse a bool from its string name (true/false), independent from the case.
static bool TryParseDouble(string rawValue, out double parsedValue, NumberStyles numberStyles=NumberStyles.Any)
Tries to parse a double independent from the culture info of the host.
static bool IsAsciiDigit(char character)
Determines whether the passed character is a ASCII digit character (0-9).
static bool TryParseFormulaStringConstant(string expression, out string value, bool enclosingQuotesRemoved=false)
Tries to parse a raw string as an Excel formula string constant. Escaped double quotes ("") are conve...
static string ToString(float input)
Transforms a float to an invariant sting.
static string NormalizeNewLines(string value)
Normalizes all newlines of a string to CR+LF.
static bool TryParseWorksheetQualifiedReference(string expression, out string worksheetName, out string reference)
Tries to parse a qualifies worksheet name and address or range expression from a raw string.
static int ParseBinaryBool(String rawValue)
Parses a bool as a binary number either based on an int (0/1) or a string expression (true/ false),...
static string ToString(int input)
Transforms an integer to an invariant sting.