1
1
using System ;
2
2
using System . Collections . Generic ;
3
3
using System . Globalization ;
4
+ using System . Linq ;
4
5
using System . Runtime . InteropServices ;
5
6
using System . Text . RegularExpressions ;
6
7
using System . Windows . Controls ;
@@ -23,6 +24,7 @@ public class Main : IPlugin, IPluginI18n, ISettingProvider
23
24
@"[ei]|[0-9]|0x[\da-fA-F]+|[\+\%\-\*\/\^\., ""]|[\(\)\|\!\[\]]" +
24
25
@")+$" , RegexOptions . Compiled ) ;
25
26
private static readonly Regex RegBrackets = new Regex ( @"[\(\)\[\]]" , RegexOptions . Compiled ) ;
27
+ private static readonly Regex ThousandGroupRegex = new Regex ( @"\B(?=(\d{3})+(?!\d))" ) ;
26
28
private static Engine MagesEngine ;
27
29
private const string comma = "," ;
28
30
private const string dot = "." ;
@@ -32,6 +34,9 @@ public class Main : IPlugin, IPluginI18n, ISettingProvider
32
34
private static Settings _settings ;
33
35
private static SettingsViewModel _viewModel ;
34
36
37
+ private string _inputDecimalSeparator ;
38
+ private bool _inputUsesGroupSeparators ;
39
+
35
40
public void Init ( PluginInitContext context )
36
41
{
37
42
Context = context ;
@@ -54,20 +59,13 @@ public List<Result> Query(Query query)
54
59
return new List < Result > ( ) ;
55
60
}
56
61
62
+ _inputDecimalSeparator = null ;
63
+ _inputUsesGroupSeparators = false ;
64
+
57
65
try
58
66
{
59
- string expression ;
60
-
61
- switch ( _settings . DecimalSeparator )
62
- {
63
- case DecimalSeparator . Comma :
64
- case DecimalSeparator . UseSystemLocale when CultureInfo . CurrentCulture . NumberFormat . NumberDecimalSeparator == "," :
65
- expression = query . Search . Replace ( "," , "." ) ;
66
- break ;
67
- default :
68
- expression = query . Search ;
69
- break ;
70
- }
67
+ var numberRegex = new Regex ( @"[\d\.,]+" ) ;
68
+ var expression = numberRegex . Replace ( query . Search , m => NormalizeNumber ( m . Value ) ) ;
71
69
72
70
var result = MagesEngine . Interpret ( expression ) ;
73
71
@@ -80,7 +78,7 @@ public List<Result> Query(Query query)
80
78
if ( ! string . IsNullOrEmpty ( result ? . ToString ( ) ) )
81
79
{
82
80
decimal roundedResult = Math . Round ( Convert . ToDecimal ( result ) , _settings . MaxDecimalPlaces , MidpointRounding . AwayFromZero ) ;
83
- string newResult = ChangeDecimalSeparator ( roundedResult , GetDecimalSeparator ( ) ) ;
81
+ string newResult = FormatResult ( roundedResult ) ;
84
82
85
83
return new List < Result >
86
84
{
@@ -115,6 +113,121 @@ public List<Result> Query(Query query)
115
113
116
114
return new List < Result > ( ) ;
117
115
}
116
+
117
+ /// <summary>
118
+ /// Parses a string representation of a number, detecting its format. It uses structural analysis
119
+ /// (checking for 3-digit groups) and falls back to system culture for ambiguous cases (e.g., "1,234").
120
+ /// It sets instance fields to remember the user's format for later output formatting.
121
+ /// </summary>
122
+ /// <returns>A normalized number string with '.' as the decimal separator for the Mages engine.</returns>
123
+ private string NormalizeNumber ( string numberStr )
124
+ {
125
+ var systemFormat = CultureInfo . CurrentCulture . NumberFormat ;
126
+ string systemDecimalSeparator = systemFormat . NumberDecimalSeparator ;
127
+
128
+ bool hasDot = numberStr . Contains ( dot ) ;
129
+ bool hasComma = numberStr . Contains ( comma ) ;
130
+
131
+ // Unambiguous case: both separators are present. The last one wins as decimal separator.
132
+ if ( hasDot && hasComma )
133
+ {
134
+ _inputUsesGroupSeparators = true ;
135
+ int lastDotPos = numberStr . LastIndexOf ( dot ) ;
136
+ int lastCommaPos = numberStr . LastIndexOf ( comma ) ;
137
+
138
+ if ( lastDotPos > lastCommaPos ) // e.g. 1,234.56
139
+ {
140
+ _inputDecimalSeparator = dot ;
141
+ return numberStr . Replace ( comma , string . Empty ) ;
142
+ }
143
+ else // e.g. 1.234,56
144
+ {
145
+ _inputDecimalSeparator = comma ;
146
+ return numberStr . Replace ( dot , string . Empty ) . Replace ( comma , dot ) ;
147
+ }
148
+ }
149
+
150
+ if ( hasComma )
151
+ {
152
+ string [ ] parts = numberStr . Split ( ',' ) ;
153
+ // If all parts after the first are 3 digits, it's a potential group separator.
154
+ bool isGroupCandidate = parts . Length > 1 && parts . Skip ( 1 ) . All ( p => p . Length == 3 ) ;
155
+
156
+ if ( isGroupCandidate )
157
+ {
158
+ // Ambiguous case: "1,234". Resolve using culture.
159
+ if ( systemDecimalSeparator == comma )
160
+ {
161
+ _inputDecimalSeparator = comma ;
162
+ return numberStr . Replace ( comma , dot ) ;
163
+ }
164
+ else
165
+ {
166
+ _inputUsesGroupSeparators = true ;
167
+ return numberStr . Replace ( comma , string . Empty ) ;
168
+ }
169
+ }
170
+ else
171
+ {
172
+ // Unambiguous decimal: "123,45" or "1,2,345"
173
+ _inputDecimalSeparator = comma ;
174
+ return numberStr . Replace ( comma , dot ) ;
175
+ }
176
+ }
177
+
178
+ if ( hasDot )
179
+ {
180
+ string [ ] parts = numberStr . Split ( '.' ) ;
181
+ bool isGroupCandidate = parts . Length > 1 && parts . Skip ( 1 ) . All ( p => p . Length == 3 ) ;
182
+
183
+ if ( isGroupCandidate )
184
+ {
185
+ if ( systemDecimalSeparator == dot )
186
+ {
187
+ _inputDecimalSeparator = dot ;
188
+ return numberStr ;
189
+ }
190
+ else
191
+ {
192
+ _inputUsesGroupSeparators = true ;
193
+ return numberStr . Replace ( dot , string . Empty ) ;
194
+ }
195
+ }
196
+ else
197
+ {
198
+ _inputDecimalSeparator = dot ;
199
+ return numberStr ; // Already in Mages-compatible format
200
+ }
201
+ }
202
+
203
+ // No separators.
204
+ return numberStr ;
205
+ }
206
+
207
+ private string FormatResult ( decimal roundedResult )
208
+ {
209
+ // Use the detected decimal separator from the input; otherwise, fall back to settings.
210
+ string decimalSeparator = _inputDecimalSeparator ?? GetDecimalSeparator ( ) ;
211
+ string groupSeparator = decimalSeparator == dot ? comma : dot ;
212
+
213
+ string resultStr = roundedResult . ToString ( CultureInfo . InvariantCulture ) ;
214
+
215
+ string [ ] parts = resultStr . Split ( '.' ) ;
216
+ string integerPart = parts [ 0 ] ;
217
+ string fractionalPart = parts . Length > 1 ? parts [ 1 ] : string . Empty ;
218
+
219
+ if ( _inputUsesGroupSeparators )
220
+ {
221
+ integerPart = ThousandGroupRegex . Replace ( integerPart , groupSeparator ) ;
222
+ }
223
+
224
+ if ( ! string . IsNullOrEmpty ( fractionalPart ) )
225
+ {
226
+ return integerPart + decimalSeparator + fractionalPart ;
227
+ }
228
+
229
+ return integerPart ;
230
+ }
118
231
119
232
private bool CanCalculate ( Query query )
120
233
{
@@ -134,27 +247,9 @@ private bool CanCalculate(Query query)
134
247
return false ;
135
248
}
136
249
137
- if ( ( query . Search . Contains ( dot ) && GetDecimalSeparator ( ) != dot ) ||
138
- ( query . Search . Contains ( comma ) && GetDecimalSeparator ( ) != comma ) )
139
- return false ;
140
-
141
250
return true ;
142
251
}
143
252
144
- private string ChangeDecimalSeparator ( decimal value , string newDecimalSeparator )
145
- {
146
- if ( String . IsNullOrEmpty ( newDecimalSeparator ) )
147
- {
148
- return value . ToString ( ) ;
149
- }
150
-
151
- var numberFormatInfo = new NumberFormatInfo
152
- {
153
- NumberDecimalSeparator = newDecimalSeparator
154
- } ;
155
- return value . ToString ( numberFormatInfo ) ;
156
- }
157
-
158
253
private static string GetDecimalSeparator ( )
159
254
{
160
255
string systemDecimalSeparator = CultureInfo . CurrentCulture . NumberFormat . NumberDecimalSeparator ;
0 commit comments