11using System ;
22using System . Collections . Generic ;
33using System . Globalization ;
4+ using System . Linq ;
45using System . Runtime . InteropServices ;
56using System . Text . RegularExpressions ;
67using System . Windows . Controls ;
@@ -23,6 +24,7 @@ public class Main : IPlugin, IPluginI18n, ISettingProvider
2324 @"[ei]|[0-9]|0x[\da-fA-F]+|[\+\%\-\*\/\^\., ""]|[\(\)\|\!\[\]]" +
2425 @")+$" , RegexOptions . Compiled ) ;
2526 private static readonly Regex RegBrackets = new Regex ( @"[\(\)\[\]]" , RegexOptions . Compiled ) ;
27+ private static readonly Regex ThousandGroupRegex = new Regex ( @"\B(?=(\d{3})+(?!\d))" ) ;
2628 private static Engine MagesEngine ;
2729 private const string comma = "," ;
2830 private const string dot = "." ;
@@ -32,6 +34,9 @@ public class Main : IPlugin, IPluginI18n, ISettingProvider
3234 private static Settings _settings ;
3335 private static SettingsViewModel _viewModel ;
3436
37+ private string _inputDecimalSeparator ;
38+ private bool _inputUsesGroupSeparators ;
39+
3540 public void Init ( PluginInitContext context )
3641 {
3742 Context = context ;
@@ -54,20 +59,13 @@ public List<Result> Query(Query query)
5459 return new List < Result > ( ) ;
5560 }
5661
62+ _inputDecimalSeparator = null ;
63+ _inputUsesGroupSeparators = false ;
64+
5765 try
5866 {
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 ) ) ;
7169
7270 var result = MagesEngine . Interpret ( expression ) ;
7371
@@ -80,7 +78,7 @@ public List<Result> Query(Query query)
8078 if ( ! string . IsNullOrEmpty ( result ? . ToString ( ) ) )
8179 {
8280 decimal roundedResult = Math . Round ( Convert . ToDecimal ( result ) , _settings . MaxDecimalPlaces , MidpointRounding . AwayFromZero ) ;
83- string newResult = ChangeDecimalSeparator ( roundedResult , GetDecimalSeparator ( ) ) ;
81+ string newResult = FormatResult ( roundedResult ) ;
8482
8583 return new List < Result >
8684 {
@@ -115,6 +113,121 @@ public List<Result> Query(Query query)
115113
116114 return new List < Result > ( ) ;
117115 }
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+ }
118231
119232 private bool CanCalculate ( Query query )
120233 {
@@ -134,27 +247,9 @@ private bool CanCalculate(Query query)
134247 return false ;
135248 }
136249
137- if ( ( query . Search . Contains ( dot ) && GetDecimalSeparator ( ) != dot ) ||
138- ( query . Search . Contains ( comma ) && GetDecimalSeparator ( ) != comma ) )
139- return false ;
140-
141250 return true ;
142251 }
143252
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-
158253 private static string GetDecimalSeparator ( )
159254 {
160255 string systemDecimalSeparator = CultureInfo . CurrentCulture . NumberFormat . NumberDecimalSeparator ;
0 commit comments