From a7393d4c9900d01a932956960c18ca0b151e46db Mon Sep 17 00:00:00 2001 From: baltasarq Date: Thu, 27 Aug 2015 10:58:30 +0200 Subject: [PATCH] Correctly deduces and manages decimal separator --- Colorado.csproj | 20 +++- Colorado.sln | 6 ++ ColoradoTests/ColoradoTests.csproj | 51 ++++++++++ ColoradoTests/Test.cs | 105 ++++++++++++++++++++ ColoradoTests/packages.config | 4 + Core/CsvDocumentPersistence.cs | 53 +++++----- Core/DecimalMark.cs | 151 +++++++++++++++++++++-------- Gui/DlgPropertiesView.cs | 5 +- Gui/MainWindowLogic.cs | 2 +- 9 files changed, 324 insertions(+), 73 deletions(-) create mode 100644 ColoradoTests/ColoradoTests.csproj create mode 100644 ColoradoTests/Test.cs create mode 100644 ColoradoTests/packages.config diff --git a/Colorado.csproj b/Colorado.csproj index cf9b460..878392f 100644 --- a/Colorado.csproj +++ b/Colorado.csproj @@ -33,11 +33,21 @@ - - - - - + + gtk-sharp-2.0 + + + gtk-sharp-2.0 + + + glib-sharp-2.0 + + + gtk-sharp-2.0 + + + gtk-sharp-2.0 + ..\CsMyLib\GtkUtil\bin\Release\GtkUtil.dll diff --git a/Colorado.sln b/Colorado.sln index 3ce7354..3addf44 100644 --- a/Colorado.sln +++ b/Colorado.sln @@ -3,6 +3,8 @@ Microsoft Visual Studio Solution File, Format Version 11.00 # Visual Studio 2010 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Colorado", "Colorado.csproj", "{12F5964C-2BDE-4379-915B-E55322B5DE75}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ColoradoTests", "ColoradoTests\ColoradoTests.csproj", "{64A2D360-A3A2-4F87-8983-EC4A18E8F053}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|AnyCPU = Debug|AnyCPU @@ -13,6 +15,10 @@ Global {12F5964C-2BDE-4379-915B-E55322B5DE75}.Debug|AnyCPU.Build.0 = Debug|Any CPU {12F5964C-2BDE-4379-915B-E55322B5DE75}.Release|AnyCPU.ActiveCfg = Release|Any CPU {12F5964C-2BDE-4379-915B-E55322B5DE75}.Release|AnyCPU.Build.0 = Release|Any CPU + {64A2D360-A3A2-4F87-8983-EC4A18E8F053}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU + {64A2D360-A3A2-4F87-8983-EC4A18E8F053}.Debug|AnyCPU.Build.0 = Debug|Any CPU + {64A2D360-A3A2-4F87-8983-EC4A18E8F053}.Release|AnyCPU.ActiveCfg = Release|Any CPU + {64A2D360-A3A2-4F87-8983-EC4A18E8F053}.Release|AnyCPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(MonoDevelopProperties) = preSolution Policies = $0 diff --git a/ColoradoTests/ColoradoTests.csproj b/ColoradoTests/ColoradoTests.csproj new file mode 100644 index 0000000..eed8899 --- /dev/null +++ b/ColoradoTests/ColoradoTests.csproj @@ -0,0 +1,51 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {64A2D360-A3A2-4F87-8983-EC4A18E8F053} + Library + ColoradoTests + ColoradoTests + v4.5 + + + true + full + false + bin\Debug + DEBUG; + prompt + 4 + false + + + full + true + bin\Release + prompt + 4 + false + + + + + ..\packages\NUnit.2.6.3\lib\nunit.framework.dll + + + + + + + + + + + + {12F5964C-2BDE-4379-915B-E55322B5DE75} + Colorado + + + \ No newline at end of file diff --git a/ColoradoTests/Test.cs b/ColoradoTests/Test.cs new file mode 100644 index 0000000..a86948e --- /dev/null +++ b/ColoradoTests/Test.cs @@ -0,0 +1,105 @@ +using NUnit.Framework; +using System; + +using Colorado.Core; + +namespace ColoradoTests { + [TestFixture] + public class TestDecimalMark { + [Test] + public void TestDecimalSeparator() { + for (int i = 0; i < DecimalMark.DecimalSeparatorChar.Count; ++i) { + Assert.AreEqual( + DecimalMark.DecimalSeparatorChar[ i ], + DecimalMark.AsChar( (DecimalMark.DecimalSeparator) i ) + ); + } + } + + [Test] + public void TestIsDecimalMark() { + Assert.AreEqual( false, DecimalMark.IsDecimalMark( 'a' ) ); + Assert.AreEqual( true, DecimalMark.IsDecimalMark( '.' ) ); + Assert.AreEqual( true, DecimalMark.IsDecimalMark( ',' ) ); + } + + [Test] + public void TestWhichDecimalMark() { + Assert.AreEqual( DecimalMark.DecimalSeparator.Point, DecimalMark.WhichDecimalMark( "4" ) ); + Assert.AreEqual( DecimalMark.DecimalSeparator.Point, DecimalMark.WhichDecimalMark( "4.5" ) ); + Assert.AreEqual( DecimalMark.DecimalSeparator.Comma, DecimalMark.WhichDecimalMark( "4,5" ) ); + + Assert.AreEqual( 0, DecimalMark.WhichDecimalMark( '.' ) ); + Assert.AreEqual( 1, DecimalMark.WhichDecimalMark( ',' ) ); + Assert.AreEqual( -1, DecimalMark.WhichDecimalMark( 'a' ) ); + } + + [Test] + public void TestIsNumber() + { + string[] testNumbers = { + "", " ", ".0", + "-.1", "-.", "0.4e-5", + "5.4", "0", "+", + "+.0", "0,6e-6", "5,6", + "hello4", "4d", "3", + "0.4e5", "0,1E45", "0,0001", + ",1", "1,", "1.", + }; + + bool[] testResults = { + false, false, true, + true, false, true, + true, true, false, + true, true, true, + false, false, true, + true, true, true, + true, true, true, + }; + + for(int i = 0; i < testResults.Length; ++i) { + double num; + bool actualResult = double.TryParse( testNumbers[ i ], out num ); + bool result = DecimalMark.IsNumber( testNumbers[ i ] ); + + Console.WriteLine( "[\"{0}\"] -> {1} == {2} == {3} == {4}?", + testNumbers[ i ], testResults[ i ], result, actualResult, num ); + + Assert.AreEqual( testResults[ i ], result ); + Assert.AreEqual( testResults[ i ], actualResult ); + } + } + + [Test] + public void TestIsRealNumber() + { + string[] testNumbers = { + "", " ", ".0", + "4e+5", "-.", "4", + "5.4", "0", "+", + "+.0", "0,6e-6", "5,6", + "hello4", "4d", "0,", + "0.4e5", "0,1E45", "0,0001", + }; + + bool[] testResults = { + false, false, true, + false, false, false, + true, false, false, + true, true, true, + false, false, true, + true, true, true, + }; + + for(int i = 0; i < testResults.Length; ++i) { + bool result = DecimalMark.IsRealNumber( testNumbers[ i ] ); + + Console.WriteLine( "[\"{0}\"] -> {1} == {2}?", + testNumbers[ i ], testResults[ i ], result ); + + Assert.AreEqual( testResults[ i ], result ); + } + } + } +} + diff --git a/ColoradoTests/packages.config b/ColoradoTests/packages.config new file mode 100644 index 0000000..d4e241a --- /dev/null +++ b/ColoradoTests/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Core/CsvDocumentPersistence.cs b/Core/CsvDocumentPersistence.cs index 72f5399..215d7ed 100644 --- a/Core/CsvDocumentPersistence.cs +++ b/Core/CsvDocumentPersistence.cs @@ -40,28 +40,7 @@ public static void PrepareFileName(ref string fileName) return; } - - public static string PrepareValue(string cell) - { - // Prepare - string toret = cell.Trim(); - - if ( toret.Length > 0 ) { - if ( toret[ 0 ] == '"' ) { - // Remove double quotes - toret = toret.Substring( 1 ); - - if ( toret[ toret.Length -1 ] == '"' ) { - toret = toret.Substring( 0, toret.Length -1 ); - } - } else { - toret = cell; - } - } - - return toret; - } - + /// /// Strips spaces from a text line, trimming it. /// Note this is needed because the TAB delimiter is part of the spaces @@ -214,6 +193,32 @@ protected void DetermineDelimiter(string line) return; } + private string FormatLoadedCell(string cell) + { + string toret = cell.Trim(); + + if ( toret.Length > 0 ) { + // Strip contents + if ( toret[ 0 ] == '"' ) { + // Remove double quotes + toret = toret.Substring( 1 ); + + if ( toret[ toret.Length -1 ] == '"' ) { + toret = toret.Substring( 0, toret.Length -1 ); + } + } else { + toret = cell; + } + + // Deduce decimal separator + if ( DecimalMark.IsRealNumber( toret ) ) { + this.Document.DecimalSeparator = DecimalMark.WhichDecimalMark( toret ); + } + } + + return toret; + } + private string[] SplitLine(string line) { var row = new List(); @@ -242,7 +247,7 @@ private string[] SplitLine(string line) if ( !inQuoted && line[ i ] == Document.DelimiterValue[ 0 ] ) { - row.Add( PrepareValue( line.Substring( pos, i - pos ) ) ); + row.Add( FormatLoadedCell( line.Substring( pos, i - pos ) ) ); pos = i + 1; } else @@ -255,7 +260,7 @@ private string[] SplitLine(string line) // Add last column if ( pos < line.Length ) { - row.Add( PrepareValue( line.Substring( pos, line.Length - pos ) ) ); + row.Add( FormatLoadedCell( line.Substring( pos, line.Length - pos ) ) ); } else if ( line[ line.Length -1 ] == Document.DelimiterValue[ 0 ] ) { diff --git a/Core/DecimalMark.cs b/Core/DecimalMark.cs index 0efd213..9762c21 100644 --- a/Core/DecimalMark.cs +++ b/Core/DecimalMark.cs @@ -43,62 +43,131 @@ public static DecimalSeparator GetFromSystem() { /// /// true if the parameter is a decimal mark; otherwise, false. /// A char possibly containing . or , - public static bool IsDecimalMark(char ch) { - return ( ch == ',' || ch == '.' ); + public static bool IsDecimalMark(char ch) { + return ( WhichDecimalMark( ch ) >= 0 ); + } + + public static int WhichDecimalMark(char ch) { + int toret = -1; + + for (int i = 0; i < DecimalSeparatorChar.Count; ++i) { + if ( DecimalSeparatorChar[ i ] == ch ) { + toret = i; + break; + } + } + + return toret; } + public static DecimalSeparator WhichDecimalMark(string s) { + DecimalSeparator toret = DecimalSeparator.Point; + + s = s.Trim(); + for (int i = 0; i < DecimalSeparatorChar.Count; ++i) { + if ( s.IndexOf( DecimalSeparatorChar[ i ] ) >= 0 ) { + toret = (DecimalSeparator) i; + break; + } + } + + return toret; + } + /// /// Determines if parameter is a number. /// /// true if parameter is a number; otherwise, false. /// A string possibly containing a number. public static bool IsNumber(string s) { - bool toret = true; + bool isReal; + + return ParseNumber( s, out isReal ); + } + + private static bool ParseNumber(string s, out bool isReal) { + bool toret = false; int pos = 0; int numMarks = 1; int numEs = 1; - - // Maybe there is a sign before the number - if ( s[ pos ] == '+' - || s[ pos ] == '-' ) - { - ++pos; - } - - // Maybe the decimal mark is at the beginning - if ( IsDecimalMark( s[ pos ] ) ) { - ++pos; - --numMarks; - } - - // Check the remaining positions - while( pos < s.Length ) { - if ( IsDecimalMark( s[ pos ] ) ) { - --numMarks; - } - else - if ( char.ToUpper( s[ pos ] ) == 'E' ) { - --numEs; - } - else - if ( !char.IsDigit( s[ pos ] ) ) { - toret = false; - break; - } - - ++pos; - } - - // More than one separator oe 'e'? - if ( numMarks < 0 - || numEs < 0 ) - { - toret = false; - } + int numDecs = 0; + + isReal = false; + if ( !string.IsNullOrWhiteSpace( s ) ) { + toret = true; + s = s.Trim(); + + // Maybe there is a sign before the number + if ( s[ pos ] == '+' + || s[ pos ] == '-' ) + { + ++pos; + } + + // Maybe the decimal mark is at the beginning + if ( pos < s.Length + && IsDecimalMark( s[ pos ] ) ) + { + ++pos; + --numMarks; + isReal = true; + } + + // Check the remaining positions + while ( pos < s.Length ) { + if ( IsDecimalMark( s[ pos ] ) ) { + --numMarks; + isReal = true; + } + else + if ( char.ToUpper( s[ pos ] ) == 'E' ) { + --numEs; + + // Maybe there is a sign after the number + if ( pos < ( s.Length - 1 ) ) { + if ( s[ pos + 1 ] == '+' + || s[ pos + 1 ] == '-' ) + { + ++pos; + } + } + } + else + if ( char.IsDigit( s[ pos ] ) ) { + ++numDecs; + } + else { + toret = false; + break; + } + + ++pos; + } + + // Do we have correct parts of the number? + if ( numMarks < 0 + || numEs < 0 + || numDecs < 1 ) + { + toret = false; + } + } + + // Can't be a real number if it is not a number + if ( !toret ) { + isReal = false; + } return toret; } + public static bool IsRealNumber(string s) { + bool isReal; + bool isNumber = ParseNumber( s, out isReal ); + + return ( isReal && isNumber ); + } + public static char AsChar(DecimalSeparator value) { return DecimalSeparatorChar[ (int) value ]; diff --git a/Gui/DlgPropertiesView.cs b/Gui/DlgPropertiesView.cs index d2ba183..42bb47c 100644 --- a/Gui/DlgPropertiesView.cs +++ b/Gui/DlgPropertiesView.cs @@ -58,6 +58,7 @@ private void BuildHeadersFrame() { this.frmHeaders = new Gtk.Frame( "Headers" ); ((Gtk.Label) this.frmHeaders.LabelWidget ).UseMarkup = true; this.listHeaders = new Gtk.TreeView(); + this.listHeaders.EnableSearch = false; swScroll.AddWithViewport( this.listHeaders ); this.frmHeaders.Add( swScroll ); @@ -85,13 +86,13 @@ private void BuildPropertiesFrame() { var hBoxDecimalMark = new Gtk.HBox( false, 2 ); // Rows - this.sbRows = new Gtk.SpinButton( 0, 100, 1 ); + this.sbRows = new Gtk.SpinButton( 0, int.MaxValue, 1 ); this.lblRows = new Gtk.Label( "Rows" ); hBoxRows.PackStart( this.lblRows, false, false, 5 ); hBoxRows.PackStart( this.sbRows, true, true, 5 ); // Columns - this.sbColumns = new Gtk.SpinButton( 1, 100, 1 ); + this.sbColumns = new Gtk.SpinButton( 1, int.MaxValue, 1 ); this.lblColumns = new Gtk.Label( "Columns" ); hBoxColumns.PackStart( this.lblColumns, false, false, 5 ); hBoxColumns.PackStart( this.sbColumns, true, true, 5 ); diff --git a/Gui/MainWindowLogic.cs b/Gui/MainWindowLogic.cs index 2c39d03..ab8d53f 100644 --- a/Gui/MainWindowLogic.cs +++ b/Gui/MainWindowLogic.cs @@ -599,8 +599,8 @@ protected void UpdateDocumentView(int oldRows, int oldColumns) private void ApplyPreferences(DlgProperties dlg) { if ( dlg.DecimalMarkValue != this.document.DecimalSeparator ) { + this.document.DecimalSeparator = dlg.DecimalMarkValue; this.ShowDocument(); - this.document.DecimalSeparator = dlg.DecimalMarkValue; } this.document.DelimiterValue = dlg.DelimiterValue;