chr
2026-04-05 fe750b791d5b517cc4e9bc8e99a9a75139a0cfba
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
/* -----------------------------------------------------
 *
 * File: NumberUnitSplitter.cs
 * Author: sven.kopacz@keysight.com
 * Created: 21.10.2016
 *
 * -----------------------------------------------------
 * 
 * Description: See class summary
 *
 * -----------------------------------------------------
 */
 
using System;
using System.Collections.Generic;
using System.Globalization;
 
namespace OpenTap
{
    /// <summary>
    /// A number unit splitter can split a text into numbers and units using whitespaces and changes
    /// from digits to characters as splitting points.
    /// </summary>
    public static class NumberUnitSplitter
    {
        /// <summary>
        /// Splits a string into pairs of numbers and units. The units are not evaluated in any way but
        /// just returned as strings. Separation points are whitespaces, any switch from digits to
        /// characters and vice versa. Dots, commas etc. are considered to belong to numbers, also the
        /// character 'e' when it is followed by a sign or digit. You can provide parsing parameters
        /// though that may render such a number invalid.
        /// For empty input strings, an empty list is returned.
        /// In case of invalid numbers, an FormatException is thrown.
        /// </summary>
        ///
        /// <param name="text">                 The text. </param>
        /// <param name="numberParseStyles">    The accepted styles for number parsing. </param>
        /// <param name="numberParseCulture">   The culture for number parsing. If omitted, DefaultThreadCurrentCulture is chosen.</param>
        ///
        /// <returns>   A List&lt;Tuple&lt;double,string&gt;&gt; </returns>
        public static List<Tuple<double, string>> Split(string text, NumberStyles numberParseStyles = NumberStyles.Any, CultureInfo numberParseCulture = null)
        {
            List<Tuple<double, string>> result = new List<Tuple<double, string>>();
 
            while (!string.IsNullOrWhiteSpace(text))
            {
                // Extract number
                string number = GetLeadingDigits(text, numberParseStyles);
                if(string.IsNullOrWhiteSpace(number))
                    throw new FormatException("Missing number");
 
                // Skip whitespace between number and unit
                int i = number.Length;
                while ((i < text.Length) && char.IsWhiteSpace(text[i]))
                    i++;
                int istart = i;
                // Extract unit
                // Everything below A is not considered a unit character
                // Everything from a-z, A-Z and everything with codes > 127 is considered a regular char
                // Remember that a char in .Net is actually a 16 bit unicode character and the unit could 
                // have something like a leading "µ", a "£" or "°C"
                int unitStartIndex = i;
                while ((i < text.Length)
                        &&
                        (
                            ((text[i] >= 'A') && (text[i] <= 'Z'))
                            ||
                            ((text[i] >= 'a') && (text[i] <= 'z'))
                            ||
                            (text[i] == '+' && i >= istart)
                            ||
                            (text[i] == '-' && i >= istart)
                            ||
                            (text[i] >= 128)
                        )
                       )
                    i++;
 
                string unit = "";
                if (i != unitStartIndex)
                    unit = text.Substring(unitStartIndex, i - unitStartIndex);
                result.Add(new Tuple<double, string>(double.Parse(number, numberParseStyles, numberParseCulture ?? CultureInfo.DefaultThreadCurrentCulture), unit));
 
                if (i >= text.Length)
                    break;
 
                text = text.Substring(i);
            }
 
            return result;
        }
        
        /// <summary>
        /// Retrieves the leading part of the string that contains digits only and an optional set of
        /// formatting signs. Note that the returned string may even consist of formatting signs only
        /// like ",.-+e+" so you should use the .NET type parser on the result determine if it is an
        /// actual number.
        /// </summary>
        ///
        /// <param name="text">                     The string to parse. </param>
        /// <param name="numberParseStyles">        This can be configured to accept the features decimal
        ///                                         delimiter, leading sign and exponent. </param>
        /// <param name="includeLeadingWhiteSpace">    true to include leading white space characters. </param>
        ///
        /// <returns>   The leading digits. </returns>
        private static string GetLeadingDigits(string text, NumberStyles numberParseStyles = NumberStyles.Any, bool includeLeadingWhiteSpace = true)
        {
            bool acceptDecimalDelimiter = (numberParseStyles & (NumberStyles.AllowDecimalPoint | NumberStyles.AllowThousands)) > 0;
            bool acceptLeadingSign = (numberParseStyles & NumberStyles.AllowLeadingSign) > 0;
            bool acceptExponent = (numberParseStyles & NumberStyles.AllowExponent) > 0;
 
            string digits = "";
            bool nextCharAllowsSign = true;
 
            foreach (char c in text)
            {
                bool acceptedChar = false;
 
                if ((c < '0') || (c > '9'))
                {
                    if (nextCharAllowsSign)
                    {
                        if ((c == '+') || (c == '-'))
                        {
                            if (acceptLeadingSign)
                                acceptedChar = true;
                        }
                    }
                    if ((c == '.') || (c == ','))
                    {
                        if (acceptDecimalDelimiter)
                            acceptedChar = true;
                    }
                    else if ((c == 'e') || (c == 'E'))
                    {
                        if (acceptExponent)
                        {
                            acceptedChar = true;
                            nextCharAllowsSign = true;
                        }
                    }
                    else if (char.IsWhiteSpace(c))
                    {
                        if (includeLeadingWhiteSpace && string.IsNullOrWhiteSpace(digits))
                            acceptedChar = true;
                    }
                }
                else
                {
                    acceptedChar = true;
                    nextCharAllowsSign = false;
                }
 
                if (!acceptedChar)
                    break;
 
                digits += c;
            }
 
            return digits;
        }
    }
}