Compare commits

21 Commits

Author SHA1 Message Date
a63f297a61 Ignore .issues directory 2025-05-11 17:36:49 +02:00
6afee1c570 Code cleanup 2024-11-17 13:54:09 +01:00
711d20bf72 Remove unused nuget JetBrains.Annotations 2024-11-16 22:19:05 +01:00
13d9218944 CsvFieldIndexer: Fix handling of escaped characters 2024-02-19 02:47:29 +01:00
52cb729c0e CsvFieldIndexer: Add tests to Save and Load 2024-02-18 16:30:45 +01:00
9429751e65 CsvLib.Tests: Fix root namespace 2024-02-18 16:04:56 +01:00
afcd404dfc Tests for CsvParser 2024-02-18 15:57:59 +01:00
66536657e2 Migrate to dotnet8 and fix warnings 2024-02-18 02:02:50 +01:00
a688f2a692 Add UI for the search feature.
Fixes #3
2023-08-21 04:41:02 +02:00
a0010593f6 CsvFieldIndexer.Search: Naive search implementation. 2023-08-21 04:40:50 +02:00
0c05215b94 Add ByteArraySearcher to CsvLib. 2023-08-21 04:25:10 +02:00
1c291807c8 CsvFieldIndexer: Fix bug saving field indexes. 2023-08-20 20:35:36 +02:00
ffc5a546c9 Replace CsvIndexer with CsvFieldIndexer. 2023-08-20 20:20:51 +02:00
aac075bb4f CsvFieldIndexer: Implement saving and loading of field indexes. 2023-08-20 20:19:41 +02:00
e38222597a Migrate CsvLib to net 7.0 2023-08-20 19:52:11 +02:00
6ed9718abb CsvFieldIndexer: Fix calculation of offsets with unicode characters.
Fixes #4
2023-08-18 15:48:11 +02:00
fb6d8d76a1 Fix some warnings 2023-08-18 02:57:36 +02:00
ddd49a0d80 CsvView: Remove unused method Index_LoadReg 2023-08-18 02:48:18 +02:00
eb5a353b74 CsvView: Allow selection of texts
Instead of disabling textboxes, we use the readonly feature.
2023-08-17 18:34:25 +02:00
21feb41598 Update Copyright dates 2023-08-17 14:48:12 +02:00
aa99fa3848 Merge AvalonaUI port
Fixes #1
2023-08-17 14:30:23 +02:00
23 changed files with 1091 additions and 579 deletions

3
.gitignore vendored
View File

@@ -25,3 +25,6 @@ Thumbs.db
obj/
[Rr]elease*/
_ReSharper*/
/.issues/

11
.idea/.idea.CsvView/.idea/avalonia.xml generated Normal file
View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AvaloniaProject">
<option name="projectPerEditor">
<map>
<entry key="CsvView/App.axaml" value="CsvView/CsvView.csproj" />
<entry key="CsvView/MainWindow.axaml" value="CsvView/CsvView.csproj" />
</map>
</option>
</component>
</project>

View File

@@ -0,0 +1,80 @@
namespace CvsLib.Tests;
public class ByteArraySearcherTests
{
[Fact]
public void Contains__EmptyNeedle_ReturnsTrue()
{
// --- Arrange
byte[] haystack = [1, 2, 3, 4, 5,];
byte[] needle = [];
ByteArraySearcher searcher = new(needle);
// --- Act
bool result = searcher.Contains(haystack);
// --- Assert
Assert.True(result);
}
[Fact]
public void Contains__NeedleAtBeginning_ReturnsTrue()
{
// --- Arrange
byte[] haystack = [1, 2, 3, 4, 5,];
byte[] needle = [1, 2, 3,];
ByteArraySearcher searcher = new(needle);
// --- Act
bool result = searcher.Contains(haystack);
// --- Assert
Assert.True(result);
}
[Fact]
public void Contains__NeedleInMiddle_ReturnsTrue()
{
// --- Arrange
byte[] haystack = [1, 2, 3, 4, 5,];
byte[] needle = [3, 4,];
ByteArraySearcher searcher = new(needle);
// --- Act
bool result = searcher.Contains(haystack);
// --- Assert
Assert.True(result);
}
[Fact]
public void Contains__NeedleAtEnd_ReturnsTrue()
{
// --- Arrange
byte[] haystack = [1, 2, 3, 4, 5,];
byte[] needle = [4, 5,];
ByteArraySearcher searcher = new(needle);
// --- Act
bool result = searcher.Contains(haystack);
// --- Assert
Assert.True(result);
}
[Fact]
public void Contains__NeedleNotPresent_ReturnsFalse()
{
// --- Arrange
byte[] haystack = [1, 2, 3, 4, 5,];
byte[] needle = [5, 6, 7,];
ByteArraySearcher searcher = new(needle);
// --- Act
bool result = searcher.Contains(haystack);
// --- Assert
Assert.False(result);
}
}

View File

@@ -1,45 +1,45 @@
using CsvLib;
using System.Text;
namespace CvsLib;
namespace CvsLib.Tests;
public class CsvFieldIndexerTests
{
#region GenerateIndex
[Fact]
public void GenerateIndex__Empty()
{
// --- Arrange
StringReader sr = new(string.Empty);
// --- Act
CsvFieldIndexer indexer = new();
indexer.GenerateIndex(sr);
// --- Assert
Assert.Single(indexer.Index);
Assert.Equal(0, indexer.Index[0]);
Assert.Empty(indexer.FieldIndex);
}
[Fact]
public void GenerateIndex__PlainText__OneRow()
{
// --- Arrange
StringReader sr = new("Hello World");
// --- Act
CsvFieldIndexer indexer = new();
indexer.GenerateIndex(sr);
// --- Assert
Assert.Equal(2, indexer.Index.Count);
Assert.Equal(0, indexer.Index[0]);
Assert.Equal(12, indexer.Index[1]);
Assert.Single(indexer.FieldIndex);
Assert.Equal(0, indexer.FieldIndex[0][0]);
Assert.Equal(10, indexer.FieldIndex[0][1]);
@@ -49,22 +49,23 @@ public class CsvFieldIndexerTests
public void GenerateIndex__TwoLinesOfPainText__TwoRows()
{
// --- Arrange
StringReader sr = new("""
Hello World
Hello World
""");
StringReader sr = new(
"""
Hello World
Hello World
""");
// --- Act
CsvFieldIndexer indexer = new();
indexer.GenerateIndex(sr);
// --- Assert
Assert.Equal(3, indexer.Index.Count);
Assert.Equal(0, indexer.Index[0]);
Assert.Equal(12, indexer.Index[1]);
Assert.Equal(24, indexer.Index[2]);
Assert.Equal(2, indexer.FieldIndex.Count);
Assert.Equal(2, indexer.FieldIndex[0].Count);
Assert.Equal(0, indexer.FieldIndex[0][0]);
@@ -78,22 +79,23 @@ public class CsvFieldIndexerTests
public void GenerateIndex__TwoLinesOfQuotedText__TwoRows()
{
// --- Arrange
StringReader sr = new("""
"Hello World"
"Hello World"
""");
StringReader sr = new(
"""
"Hello World"
"Hello World"
""");
// --- Act
CsvFieldIndexer indexer = new();
indexer.GenerateIndex(sr);
// --- Assert
Assert.Equal(3, indexer.Index.Count);
Assert.Equal(0, indexer.Index[0]);
Assert.Equal(14, indexer.Index[1]);
Assert.Equal(28, indexer.Index[2]);
Assert.Equal(2, indexer.FieldIndex.Count);
Assert.Equal(2, indexer.FieldIndex[0].Count);
Assert.Equal(1, indexer.FieldIndex[0][0]);
@@ -107,22 +109,23 @@ public class CsvFieldIndexerTests
public void GenerateIndex__TwoLinesWithTwoQuotedColumns__TwoRowsTwoFields()
{
// --- Arrange
StringReader sr = new("""
"Hello","World"
"Hello","World"
""");
StringReader sr = new(
"""
"Hello","World"
"Hello","World"
""");
// --- Act
CsvFieldIndexer indexer = new();
indexer.GenerateIndex(sr);
// --- Assert
Assert.Equal(3, indexer.Index.Count);
Assert.Equal(0, indexer.Index[0]);
Assert.Equal(16, indexer.Index[1]);
Assert.Equal(32, indexer.Index[2]);
Assert.Equal(2, indexer.FieldIndex.Count);
Assert.Equal(4, indexer.FieldIndex[0].Count);
Assert.Equal(1, indexer.FieldIndex[0][0]);
@@ -136,7 +139,206 @@ public class CsvFieldIndexerTests
Assert.Equal(29, indexer.FieldIndex[1][3]);
}
[Fact]
public void GenerateIndex__TwoLinesWithOneQuotedColumnsWithEscapedQuotes__TwoRows()
{
// --- Arrange
StringReader sr = new(
"""
"Hello \"World\""
"Hello \"World\""
""");
// --- Act
CsvFieldIndexer indexer = new();
indexer.GenerateIndex(sr);
// --- Assert
Assert.Equal(3, indexer.Index.Count);
Assert.Equal(0, indexer.Index[0]);
Assert.Equal(18, indexer.Index[1]);
Assert.Equal(36, indexer.Index[2]);
Assert.Equal(2, indexer.FieldIndex.Count);
Assert.Equal(2, indexer.FieldIndex[0].Count);
Assert.Equal(1, indexer.FieldIndex[0][0]);
Assert.Equal(15, indexer.FieldIndex[0][1]);
Assert.Equal(2, indexer.FieldIndex[1].Count);
Assert.Equal(19, indexer.FieldIndex[1][0]);
Assert.Equal(33, indexer.FieldIndex[1][1]);
}
[Fact]
public void GenerateIndex__TwoLinesWithOneQuotedColumnsWithManyEscapedQuotes__TwoRows()
{
// --- Arrange
StringReader sr = new(
"""
"Hello \"World\""
"Hello \"World\"\"\""
""");
// --- Act
CsvFieldIndexer indexer = new();
indexer.GenerateIndex(sr);
// --- Assert
Assert.Equal(3, indexer.Index.Count);
Assert.Equal(0, indexer.Index[0]);
Assert.Equal(18, indexer.Index[1]);
Assert.Equal(40, indexer.Index[2]);
Assert.Equal(2, indexer.FieldIndex.Count);
Assert.Equal(2, indexer.FieldIndex[0].Count);
Assert.Equal(1, indexer.FieldIndex[0][0]);
Assert.Equal(15, indexer.FieldIndex[0][1]);
Assert.Equal(2, indexer.FieldIndex[1].Count);
Assert.Equal(19, indexer.FieldIndex[1][0]);
Assert.Equal(37, indexer.FieldIndex[1][1]);
}
[Fact]
public void GenerateIndex__TwoLinesWithTwoQuotedColumnsWithUnicode__TwoRowsTwoFields()
{
// --- Arrange
StringReader sr = new(
"""
"Hélló","Wórld"
"Hélló","Wórld"
""");
// --- Act
CsvFieldIndexer indexer = new();
indexer.GenerateIndex(sr);
// --- Assert
Assert.Equal(3, indexer.Index.Count);
Assert.Equal(0, indexer.Index[0]);
Assert.Equal(19, indexer.Index[1]);
Assert.Equal(38, indexer.Index[2]);
Assert.Equal(2, indexer.FieldIndex.Count);
Assert.Equal(4, indexer.FieldIndex[0].Count);
Assert.Equal(1, indexer.FieldIndex[0][0]);
Assert.Equal(7, indexer.FieldIndex[0][1]);
Assert.Equal(11, indexer.FieldIndex[0][2]);
Assert.Equal(16, indexer.FieldIndex[0][3]);
Assert.Equal(4, indexer.FieldIndex[1].Count);
Assert.Equal(20, indexer.FieldIndex[1][0]);
Assert.Equal(26, indexer.FieldIndex[1][1]);
Assert.Equal(30, indexer.FieldIndex[1][2]);
Assert.Equal(35, indexer.FieldIndex[1][3]);
}
#endregion GenerateIndex
}
#region Search
[Fact]
public void Search__TwoLinesWithTwoQuotedColumns__OneIndexFirstRow()
{
// --- Arrange
const string strText =
"""
"Hello","test"
"Hello","World"
""";
StringReader sr = new(strText);
CsvFieldIndexer indexer = new();
indexer.GenerateIndex(sr);
// --- Act
byte[] bText = Encoding.UTF8.GetBytes(strText);
MemoryStream ms = new(bText);
List<long> indexes = indexer.Search(ms, "test");
// --- Assert
Assert.Single(indexes);
Assert.Equal(0, indexes[0]);
}
[Fact]
public void Search__TwoLinesWithTwoQuotedColumns__OneIndexSecondRow()
{
// --- Arrange
const string strText =
"""
"Hello","World"
"Hello","test"
""";
StringReader sr = new(strText);
CsvFieldIndexer indexer = new();
indexer.GenerateIndex(sr);
// --- Act
byte[] bText = Encoding.UTF8.GetBytes(strText);
MemoryStream ms = new(bText);
List<long> indexes = indexer.Search(ms, "test");
// --- Assert
Assert.Single(indexes);
Assert.Equal(16, indexes[0]);
}
[Fact]
public void Search__TwoLinesWithTwoQuotedColumnsTwoMatches__OneIndexSecondRow()
{
// --- Arrange
const string strText =
"""
"Hello","World"
"test","test"
""";
StringReader sr = new(strText);
CsvFieldIndexer indexer = new();
indexer.GenerateIndex(sr);
// --- Act
byte[] bText = Encoding.UTF8.GetBytes(strText);
MemoryStream ms = new(bText);
List<long> indexes = indexer.Search(ms, "test");
// --- Assert
Assert.Single(indexes);
Assert.Equal(16, indexes[0]);
}
#endregion Search
#region Save & Load
[Fact]
public void Save__TwoLinesWithTwoQuotedColumnsTwoMatchesSave__LoadsCorrectly()
{
// --- Arrange
StringReader sr = new(
"""
"Hélló","Wórld"
"Hélló","Wórld"
""");
// --- Act
CsvFieldIndexer indexer = new();
indexer.GenerateIndex(sr);
MemoryStream stream = new();
indexer.Save(stream);
byte[] savedData = stream.ToArray();
CsvFieldIndexer indexer2 = new();
MemoryStream stream2 = new(savedData);
bool loadResult = indexer2.Load(stream2);
// --- Assert
Assert.True(loadResult);
Assert.Equal(indexer.Index, indexer2.Index);
Assert.Equal(indexer.FieldIndex, indexer2.FieldIndex);
}
#endregion Save & Load
}

View File

@@ -1,10 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>CvsLib</RootNamespace>
<RootNamespace>CvsLib.Tests</RootNamespace>
<IsPackable>false</IsPackable>
</PropertyGroup>

View File

@@ -0,0 +1,175 @@
namespace CvsLib.Tests;
public class CsvParserTest
{
#region Parse
[Fact]
public void Parse__Empty__Empty()
{
// --- Arrange
StringReader sr = new(string.Empty);
// --- Act
CsvParser parser = new();
parser.Parse(sr);
// --- Assert
Assert.Empty(parser.Data);
}
[Fact]
public void Parse__PlainText__OneRowOneColumn()
{
// --- Arrange
StringReader sr = new("Hello World");
// --- Act
CsvParser parser = new();
parser.Parse(sr);
// --- Assert
Assert.Single(parser.Data);
Assert.Single(parser.Data[0]);
Assert.Equal("Hello World", parser.Data[0][0]);
}
[Fact]
public void Parse__TwoLinesOfPainText__TwoRows()
{
// --- Arrange
StringReader sr = new(
"""
Hello World
Hello World
""");
// --- Act
CsvParser parser = new();
parser.Parse(sr);
// --- Assert
Assert.Equal(2, parser.Data.Count);
Assert.Single(parser.Data[0]);
Assert.Equal("Hello World", parser.Data[0][0]);
Assert.Single(parser.Data[1]);
Assert.Equal("Hello World", parser.Data[1][0]);
}
[Fact]
public void Parse__TwoLinesOfQuotedText__TwoRows()
{
// --- Arrange
StringReader sr = new(
"""
"Hello World"
"Hello World"
""");
// --- Act
CsvParser parser = new();
parser.Parse(sr);
// --- Assert
Assert.Equal(2, parser.Data.Count);
Assert.Single(parser.Data[0]);
Assert.Equal("Hello World", parser.Data[0][0]);
Assert.Single(parser.Data[1]);
Assert.Equal("Hello World", parser.Data[1][0]);
}
[Fact]
public void Parse__TwoLinesWithTwoQuotedColumns__TwoRowsTwoFields()
{
// --- Arrange
StringReader sr = new(
"""
"Hello","World"
"Hello","World"
""");
// --- Act
CsvParser parser = new();
parser.Parse(sr);
// --- Assert
Assert.Equal(2, parser.Data.Count);
Assert.Equal(2, parser.Data[0].Count);
Assert.Equal("Hello", parser.Data[0][0]);
Assert.Equal("World", parser.Data[0][1]);
Assert.Equal(2, parser.Data[1].Count);
Assert.Equal("Hello", parser.Data[1][0]);
Assert.Equal("World", parser.Data[1][1]);
}
[Fact]
public void Parse__TwoLinesWithOneQuotedColumnsWithEscapedQuotes__TwoRows()
{
// --- Arrange
StringReader sr = new(
"""
"Hello \"World\""
"Hello \"World\""
""");
// --- Act
CsvParser parser = new();
parser.Parse(sr);
// --- Assert
Assert.Equal(2, parser.Data.Count);
Assert.Single(parser.Data[0]);
Assert.Equal("Hello \"World\"", parser.Data[0][0]);
Assert.Single(parser.Data[1]);
Assert.Equal("Hello \"World\"", parser.Data[1][0]);
}
[Fact]
public void Parse__TwoLinesWithOneQuotedColumnsWithManyEscapedQuotes__TwoRows()
{
// --- Arrange
StringReader sr = new(
"""
"Hello \"World\""
"Hello \"World\"\"\""
""");
// --- Act
CsvParser parser = new();
parser.Parse(sr);
// --- Assert
Assert.Equal(2, parser.Data.Count);
Assert.Single(parser.Data[0]);
Assert.Equal("Hello \"World\"", parser.Data[0][0]);
Assert.Single(parser.Data[1]);
Assert.Equal("Hello \"World\"\"\"", parser.Data[1][0]);
}
[Fact]
public void GenerateIndex__TwoLinesWithTwoQuotedColumnsWithUnicode__TwoRowsTwoFields()
{
// --- Arrange
StringReader sr = new(
"""
"Hélló","Wórld"
"Hélló","Wórld"
""");
// --- Act
CsvParser parser = new();
parser.Parse(sr);
// --- Assert
Assert.Equal(2, parser.Data.Count);
Assert.Equal(2, parser.Data[0].Count);
Assert.Equal("Hélló", parser.Data[0][0]);
Assert.Equal("Wórld", parser.Data[0][1]);
Assert.Equal(2, parser.Data[1].Count);
Assert.Equal("Hélló", parser.Data[1][0]);
Assert.Equal("Wórld", parser.Data[1][1]);
}
#endregion Parse
}

View File

@@ -1 +1,2 @@
global using Xunit;
global using CsvLib;

View File

@@ -1,54 +1,52 @@
using System;
using System.IO;
using System.Text;
namespace CsvLib
namespace CsvLib;
public class BufferedTextReader : TextReader
{
public class BufferedTextReader : TextReader
private readonly TextReader _baseReader;
private readonly StringBuilder _sbBuffer = new();
private readonly Encoding _currentEncoding = Encoding.Default;
public BufferedTextReader(TextReader baseReader)
{
private readonly TextReader _baseReader;
private int _position;
private readonly StringBuilder _sbBuffer = new StringBuilder();
public BufferedTextReader(TextReader baseReader)
_baseReader = baseReader;
if (baseReader is StreamReader streamReader)
{
_baseReader = baseReader;
}
public override int Read()
{
_position++;
int read = _baseReader.Read();
if (read != -1)
{
_sbBuffer.Append((char)read);
}
return read;
}
public override int Read(char[] buffer, int index, int count)
{
throw new NotImplementedException("Read buffered method on BufferedTextReader");
}
public override int Peek()
{
return _baseReader.Peek();
}
public int Position
{
get { return _position; }
}
public string GetBuffer()
{
return _sbBuffer.ToString();
}
public void CleanBuffer()
{
_sbBuffer.Clear();
_currentEncoding = streamReader.CurrentEncoding;
}
}
}
public override int Read()
{
int read = _baseReader.Read();
if (read > 127)
{
int count = _currentEncoding.GetByteCount(((char)read).ToString());
Position += count;
}
else
{
Position++;
}
if (read != -1)
{
_sbBuffer.Append((char)read);
}
return read;
}
public int Position { get; private set; }
public string GetBuffer()
{
return _sbBuffer.ToString();
}
public void CleanBuffer()
{
_sbBuffer.Clear();
}
}

View File

@@ -0,0 +1,38 @@
namespace CsvLib;
public class ByteArraySearcher
{
private readonly byte[] _needle;
public ByteArraySearcher(byte[] needle)
{
_needle = needle;
}
public bool Contains(byte[] haystack)
{
return Contains(haystack, haystack.Length);
}
public bool Contains(byte[] haystack, int length)
{
// TODO: Implement the Boyer-Moore algorithm
for (int i = 0; i <= length - _needle.Length; i++)
{
bool found = true;
for (int j = 0; j < _needle.Length; j++)
{
if (haystack[i + j] != _needle[j])
{
found = false;
break;
}
}
if (found) { return true; }
}
return false;
}
}

View File

@@ -3,203 +3,363 @@ using System.Collections.Generic;
using System.IO;
using System.Text;
namespace CsvLib
namespace CsvLib;
public class CsvFieldIndexer
{
public class CsvFieldIndexer
#region Declarations
private const byte FileFormatVersion = 1;
private bool _insideString;
private Encoding _currentEncoding = Encoding.Default;
private readonly char _separator;
private readonly char _quoteChar;
private readonly char _escapeChar;
#endregion Declarations
#region Life cycle
public CsvFieldIndexer(char separator = ',', char quoteChar = '"', char escapeChar = '\\')
{
private bool _insideString;
_separator = separator;
_quoteChar = quoteChar;
_escapeChar = escapeChar;
}
#endregion Life cycle
private readonly char _separator;
private readonly char _quoteChar;
private readonly char _escapeChar;
#region Properties
private List<long> _index = new();
public CsvFieldIndexer(char separator = ',', char quoteChar = '"', char escapeChar = '\\')
public List<long> Index { get { return _index; } }
private List<List<long>> _fieldIndex = new();
public List<List<long>> FieldIndex { get { return _fieldIndex; } }
#endregion Properties
#region Parsing
private void DummyParser(string line)
{
for (int i = 0; i < line.Length; i++)
{
_separator = separator;
_quoteChar = quoteChar;
_escapeChar = escapeChar;
}
private List<long> _index = new List<long>();
public List<long> Index { get { return _index; } }
private List<List<long>> _fieldIndex = new List<List<long>>();
public List<List<long>> FieldIndex { get { return _fieldIndex; } }
private void DummyParser(string line)
{
for (int i = 0; i < line.Length; i++)
char c = line[i];
if (c == _separator && _insideString == false)
{
char c = line[i];
if (c == _separator && _insideString == false)
{
continue;
}
if (c == _quoteChar && _insideString == false)
{
_insideString = true;
continue;
}
if (c == _quoteChar && _insideString)
{
_insideString = false;
continue;
}
if (c == _escapeChar && _insideString)
{
i++;
c = line[i];
}
continue;
}
if (c == _quoteChar && _insideString == false)
{
_insideString = true;
continue;
}
if (c == _quoteChar && _insideString)
{
_insideString = false;
continue;
}
if (c == _escapeChar && _insideString)
{
i++;
}
}
}
private List<long> ParseLineIndex(string line, long lineOffset)
private List<long> ParseLineIndex(string line, long lineOffset)
{
List<long> fieldPositions = new();
long? fieldStartPosition = null;
long? fieldEndPosition = null;
int unicodeDelta = 0;
for (int i = 0; i < line.Length; i++)
{
List<long> fieldPositions = new List<long>();
long? fieldStartPosition = null;
long? fieldEndPosition = null;
for (int i = 0; i < line.Length; i++)
char c = line[i];
if (c == _separator && _insideString == false)
{
char c = line[i];
if (c == _separator && _insideString == false)
{
if (fieldStartPosition != null)
{
fieldPositions.Add((long)fieldStartPosition);
fieldPositions.Add((long)fieldEndPosition);
}
fieldStartPosition = null;
fieldEndPosition = null;
}
else if (c == _quoteChar && _insideString == false)
{
_insideString = true;
}
else if (c == _quoteChar && _insideString)
{
_insideString = false;
}
else if (c == _escapeChar && _insideString)
{
i++;
c = line[i];
}
else if ((c == '\n' || c == '\r') && _insideString == false)
{
break;
}
else
{
long absolutePosition = lineOffset + i;
if (fieldStartPosition == null) { fieldStartPosition = absolutePosition; }
fieldEndPosition = absolutePosition;
}
}
if (_insideString == false)
{
if (fieldStartPosition != null)
if (fieldStartPosition != null && fieldEndPosition != null)
{
fieldPositions.Add((long)fieldStartPosition);
fieldPositions.Add((long)fieldEndPosition);
}
fieldStartPosition = null;
fieldEndPosition = null;
}
return fieldPositions;
}
public void GenerateIndex(string file)
{
using (FileStream stream = new FileStream(file, FileMode.Open))
using (StreamReader streamReader = new StreamReader(stream, Encoding.Default, true, 4096))
else if (c == _quoteChar && _insideString == false)
{
GenerateIndex(streamReader);
_insideString = true;
}
}
public void GenerateIndex(TextReader textReader)
{
_insideString = false;
_index.Clear();
_index.Add(0);
int idxRow = 0;
using (BufferedTextReader reader = new BufferedTextReader(textReader))
else if (c == _quoteChar && _insideString)
{
string currentLine;
while ((currentLine = reader.ReadLine()) != null)
{
DummyParser(currentLine);
if (_insideString) { continue; }
string fullLine = reader.GetBuffer();
reader.CleanBuffer();
List<long> fieldIndexes = ParseLineIndex(fullLine, _index[idxRow]);
_fieldIndex.Add(fieldIndexes);
_index.Add(reader.Position);
idxRow++;
}
_insideString = false;
}
}
private void Index_SaveFile(string indexFile)
{
if (File.Exists(indexFile))
else if (c == _escapeChar && _insideString)
{
File.Delete(indexFile);
i++;
long absolutePosition = lineOffset + i + unicodeDelta;
fieldStartPosition ??= absolutePosition;
fieldEndPosition = absolutePosition;
}
Stream streamOut = File.Open(indexFile, FileMode.Create);
using (BinaryWriter binWriter = new BinaryWriter(streamOut))
else if ((c == '\n' || c == '\r') && _insideString == false)
{
binWriter.Write(_index.Count);
for (int i = 0; i < _index.Count; i++)
{
binWriter.Write(_index[i]);
}
}
streamOut.Close();
}
private static List<long> Index_LoadFile(string indexFile)
{
List<long> tempIndex = new List<long>();
Stream streamIn = File.Open(indexFile, FileMode.Open);
using (BinaryReader binReader = new BinaryReader(streamIn))
{
int numRegs = binReader.ReadInt32();
for (int i = 0; i < numRegs; i++)
{
long value = binReader.ReadInt64();
tempIndex.Add(value);
}
}
streamIn.Close();
return tempIndex;
}
public void LoadIndexOfFile(string file)
{
DateTime dtFile = File.GetCreationTime(file);
string indexFile = $"{file}.idx";
if (File.Exists(indexFile) && File.GetCreationTime(indexFile) > dtFile)
{
_index = Index_LoadFile(indexFile);
break;
}
else
{
// Generate index
DateTime dtNow = DateTime.UtcNow;
GenerateIndex(file);
TimeSpan tsGenIndex = DateTime.UtcNow - dtNow;
// Save Index if expensive generation
if (tsGenIndex.TotalSeconds > 2)
if (c > 127)
{
Index_SaveFile(indexFile);
unicodeDelta += _currentEncoding.GetByteCount(c.ToString()) - 1;
}
long absolutePosition = lineOffset + i + unicodeDelta;
fieldStartPosition ??= absolutePosition;
fieldEndPosition = absolutePosition;
}
}
if (_insideString == false)
{
if (fieldStartPosition != null && fieldEndPosition != null)
{
fieldPositions.Add((long)fieldStartPosition);
fieldPositions.Add((long)fieldEndPosition);
}
}
return fieldPositions;
}
#endregion Parsing
#region GenerateIndex
private void GenerateIndex(string file)
{
using FileStream stream = new(file, FileMode.Open);
using StreamReader streamReader = new(stream, Encoding.Default, true, 4096);
GenerateIndex(streamReader);
stream.Close();
}
public void GenerateIndex(TextReader textReader)
{
_insideString = false;
_index.Clear();
_index.Add(0);
int idxRow = 0;
if (textReader is StreamReader streamReader)
{
_currentEncoding = streamReader.CurrentEncoding;
}
using BufferedTextReader reader = new(textReader);
while (reader.ReadLine() is { } currentLine)
{
DummyParser(currentLine);
if (_insideString) { continue; }
string fullLine = reader.GetBuffer();
reader.CleanBuffer();
List<long> fieldIndexes = ParseLineIndex(fullLine, _index[idxRow]);
_fieldIndex.Add(fieldIndexes);
_index.Add(reader.Position);
idxRow++;
}
}
#endregion GenerateIndex
#region Save
public void Save(Stream streamOut)
{
using BinaryWriter binWriter = new(streamOut);
binWriter.Write((byte)'C');
binWriter.Write((byte)'S');
binWriter.Write((byte)'V');
binWriter.Write(FileFormatVersion);
binWriter.Write(_index.Count);
foreach (long currentIndex in _index)
{
binWriter.Write(currentIndex);
}
binWriter.Write(_fieldIndex.Count);
foreach (List<long> currentFieldIndex in _fieldIndex)
{
binWriter.Write(currentFieldIndex.Count);
foreach (long fieldIndex in currentFieldIndex)
{
binWriter.Write(fieldIndex);
}
}
}
private void SaveFile(string indexFile)
{
if (File.Exists(indexFile))
{
File.Delete(indexFile);
}
Stream streamOut = File.Open(indexFile, FileMode.Create);
Save(streamOut);
streamOut.Close();
}
#endregion Save
#region Load
public bool Load(Stream streamIn)
{
using BinaryReader binReader = new(streamIn);
byte magik0 = binReader.ReadByte();
byte magik1 = binReader.ReadByte();
byte magik2 = binReader.ReadByte();
if (magik0 != (byte)'C' || magik1 != (byte)'S' || magik2 != (byte)'V') { return false; }
byte fileVersion = binReader.ReadByte();
if (fileVersion != FileFormatVersion) { return false; }
int numIndexes = binReader.ReadInt32();
List<long> tempIndex = new(numIndexes);
for (int i = 0; i < numIndexes; i++)
{
long value = binReader.ReadInt64();
tempIndex.Add(value);
}
int numFieldIndexes = binReader.ReadInt32();
List<List<long>> tempFieldIndex = new(numFieldIndexes);
for (int j = 0; j < numFieldIndexes; j++)
{
int numCurrentFieldIndexes = binReader.ReadInt32();
List<long> currentFieldIndex = new(numCurrentFieldIndexes);
for (int i = 0; i < numCurrentFieldIndexes; i++)
{
long value = binReader.ReadInt64();
currentFieldIndex.Add(value);
}
tempFieldIndex.Add(currentFieldIndex);
}
_index = tempIndex;
_fieldIndex = tempFieldIndex;
return true;
}
private bool LoadFile(string indexFile)
{
if (File.Exists(indexFile) == false)
{
return false;
}
Stream streamIn = File.Open(indexFile, FileMode.Open);
try
{
if (Load(streamIn) == false) return false;
}
catch (Exception)
{
// NON NON NOM
return false;
}
finally
{
streamIn.Close();
}
return true;
}
public void LoadIndexOfFile(string file)
{
DateTime dtFile = File.GetCreationTime(file);
string indexFile = $"{file}.idx";
if (File.Exists(indexFile) && File.GetCreationTime(indexFile) > dtFile)
{
if (LoadFile(indexFile)) { return; }
}
// Generate index
DateTime dtNow = DateTime.UtcNow;
GenerateIndex(file);
TimeSpan tsGenIndex = DateTime.UtcNow - dtNow;
// Save Index if expensive generation
if (tsGenIndex.TotalSeconds > 2)
{
SaveFile(indexFile);
}
}
#endregion Load
#region Search
public List<long> Search(Stream streamIn, string textToSearch, Action<float>? notifyProgress = null)
{
// TODO: Use MemoryMappedFile for better IO performance
DateTime datePrevious = DateTime.UtcNow;
List<long> newIndexes = new();
byte[] bText = Encoding.UTF8.GetBytes(textToSearch);
ByteArraySearcher searcher = new(bText);
byte[] buffer = new byte[1024];
for (int j = 0; j < _fieldIndex.Count; j++)
{
for (int i = 0; i < _fieldIndex[j].Count; i += 2)
{
TimeSpan tsElapsed = DateTime.UtcNow - datePrevious;
if (tsElapsed.TotalMilliseconds > 200)
{
datePrevious = DateTime.UtcNow;
notifyProgress?.Invoke(j / (float)_fieldIndex.Count);
}
long offset = _fieldIndex[j][i];
int length = (int)(_fieldIndex[j][i + 1] - offset) + 1;
if (buffer.Length < length)
{
buffer = new byte[length];
}
streamIn.Seek(offset, SeekOrigin.Begin);
int read = streamIn.Read(buffer, 0, length);
if (read != length) { throw new Exception($"Search: Expected {length} bytes, but read {read}"); }
bool matches = searcher.Contains(buffer, length);
if (matches == false) { continue; }
newIndexes.Add(_index[j]);
break;
}
}
return newIndexes;
}
public List<long> SearchFile(string fileName, string textToSearch, Action<float>? notifyProgress = null)
{
List<long> index;
using FileStream streamIn = new(fileName, FileMode.Open);
try
{
index = Search(streamIn, textToSearch, notifyProgress);
}
finally
{
streamIn.Close();
}
return index;
}
#endregion Search
}

View File

@@ -1,137 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace CsvLib
{
public class CsvIndexer
{
private bool _insideString;
private readonly char _separator;
private readonly char _quoteChar;
private readonly char _escapeChar;
public CsvIndexer(char separator = ',', char quoteChar = '"', char escapeChar = '\\')
{
_separator = separator;
_quoteChar = quoteChar;
_escapeChar = escapeChar;
}
private List<long> _index = new List<long>();
public List<long> Index { get { return _index; } }
private void DummyParser(string line)
{
for (int i = 0; i < line.Length; i++)
{
char c = line[i];
if (c == _separator && _insideString == false)
{
continue;
}
if (c == _quoteChar && _insideString == false)
{
_insideString = true;
continue;
}
if (c == _quoteChar && _insideString)
{
_insideString = false;
continue;
}
if (c == _escapeChar && _insideString)
{
i++;
c = line[i];
}
}
}
public void GenerateIndex(string file)
{
_insideString = false;
_index.Clear();
using (FileStream stream = new FileStream(file, FileMode.Open))
using (StreamReader streamReader = new StreamReader(stream, Encoding.Default, true, 4096))
using (TrackingTextReader reader = new TrackingTextReader(streamReader))
{
string currentLine;
if (_insideString == false)
{
_index.Add(reader.Position);
}
while ((currentLine = reader.ReadLine()) != null)
{
DummyParser(currentLine);
if (_insideString == false)
{
_index.Add(reader.Position);
}
}
}
}
private void Index_SaveFile(string indexFile)
{
if (File.Exists(indexFile))
{
File.Delete(indexFile);
}
Stream streamOut = File.Open(indexFile, FileMode.Create);
using (BinaryWriter binWriter = new BinaryWriter(streamOut))
{
binWriter.Write(_index.Count);
for (int i = 0; i < _index.Count; i++)
{
binWriter.Write(_index[i]);
}
}
streamOut.Close();
}
private static List<long> Index_LoadFile(string indexFile)
{
List<long> tempIndex = new List<long>();
Stream streamIn = File.Open(indexFile, FileMode.Open);
using (BinaryReader binReader = new BinaryReader(streamIn))
{
int numRegs = binReader.ReadInt32();
for (int i = 0; i < numRegs; i++)
{
long value = binReader.ReadInt64();
tempIndex.Add(value);
}
}
streamIn.Close();
return tempIndex;
}
public void LoadIndexOfFile(string file)
{
DateTime dtFile = File.GetCreationTime(file);
string indexFile = $"{file}.idx";
if (File.Exists(indexFile) && File.GetCreationTime(indexFile) > dtFile)
{
_index = Index_LoadFile(indexFile);
}
else
{
// Generate index
DateTime dtNow = DateTime.UtcNow;
GenerateIndex(file);
TimeSpan tsGenIndex = DateTime.UtcNow - dtNow;
// Save Index if expensive generation
if (tsGenIndex.TotalSeconds > 2)
{
Index_SaveFile(indexFile);
}
}
}
}
}

View File

@@ -1,7 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>11</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@@ -2,108 +2,103 @@
using System.IO;
using System.Text;
namespace CsvLib
namespace CsvLib;
public class CsvParser
{
public class CsvParser
private bool _insideString;
private readonly char _separator;
private readonly char _quoteChar;
private readonly char _escapeChar;
public CsvParser(char separator = ',', char quoteChar = '"', char escapeChar = '\\')
{
private bool _insideString;
_separator = separator;
_quoteChar = quoteChar;
_escapeChar = escapeChar;
}
private readonly char _separator;
private readonly char _quoteChar;
private readonly char _escapeChar;
private List<List<string>> _data = new();
public CsvParser(char separator = ',', char quoteChar = '"', char escapeChar = '\\')
private List<string>? _currentReg;
private StringBuilder? _currentCell;
public List<List<string>> Data
{
get { return _data; }
}
private void ParseLine(string line)
{
_currentReg ??= new List<string>();
_currentCell ??= new StringBuilder();
for (int i = 0; i < line.Length; i++)
{
_separator = separator;
_quoteChar = quoteChar;
_escapeChar = escapeChar;
}
private List<List<string>> _data = new List<List<string>>();
private List<string> _currentReg;
StringBuilder _currentCell;
public List<List<string>> Data
{
get { return _data; }
}
public void ParseLine(string line)
{
if (_currentReg == null)
{
_currentReg = new List<string>();
}
if (_currentCell == null)
{
_currentCell = new StringBuilder();
}
for (int i = 0; i < line.Length; i++)
{
char c = line[i];
if (c == _separator && _insideString == false)
{
_currentReg.Add(_currentCell.ToString());
_currentCell.Clear();
continue;
}
if (c == _quoteChar && _insideString == false)
{
_insideString = true;
continue;
}
if (c == _quoteChar && _insideString)
{
_insideString = false;
continue;
}
if (c == _escapeChar && _insideString)
{
i++;
if (i == line.Length) { break; }
c = line[i];
}
_currentCell.Append(c);
}
if (_insideString)
{
_currentCell.Append('\n');
}
else
char c = line[i];
if (c == _separator && _insideString == false)
{
_currentReg.Add(_currentCell.ToString());
_currentCell.Clear();
_data.Add(_currentReg);
_currentReg = null;
continue;
}
if (c == _quoteChar && _insideString == false)
{
_insideString = true;
continue;
}
if (c == _quoteChar && _insideString)
{
_insideString = false;
continue;
}
if (c == _escapeChar && _insideString)
{
i++;
if (i == line.Length) { break; }
c = line[i];
}
_currentCell.Append(c);
}
public void ParseFile(string file, long offset = 0, int count = 0)
if (_insideString)
{
_insideString = false;
_data = new List<List<string>>();
_currentReg = null;
FileStream stream = new FileStream(file, FileMode.Open);
stream.Seek(offset, SeekOrigin.Begin);
using (StreamReader reader = new StreamReader(stream, Encoding.Default, true, 4096))
{
string currentLine;
while ((currentLine = reader.ReadLine()) != null)
{
ParseLine(currentLine);
if (count > 0 && Data.Count == count)
{
break;
}
}
}
stream.Close();
_currentCell.Append('\n');
}
else
{
_currentReg.Add(_currentCell.ToString());
_currentCell.Clear();
_data.Add(_currentReg);
_currentReg = null;
}
}
}
public void Parse(TextReader reader, int count = 0)
{
_insideString = false;
_data = new List<List<string>>();
_currentReg = null;
while (reader.ReadLine() is { } currentLine)
{
ParseLine(currentLine);
if (count > 0 && _data.Count == count)
{
break;
}
}
}
public void ParseFile(string file, long offset = 0, int count = 0)
{
FileStream stream = new(file, FileMode.Open);
stream.Seek(offset, SeekOrigin.Begin);
using StreamReader reader = new(stream, Encoding.Default, true, 4096);
Parse(reader, count);
stream.Close();
}
}

View File

@@ -1,37 +0,0 @@
using System;
using System.IO;
namespace CsvLib
{
public class TrackingTextReader : TextReader
{
private readonly TextReader _baseReader;
private int _position;
public TrackingTextReader(TextReader baseReader)
{
_baseReader = baseReader;
}
public override int Read()
{
_position++;
return _baseReader.Read();
}
public override int Read(char[] buffer, int index, int count)
{
throw new NotImplementedException("Read buffered method on TrackingTextReader");
}
public override int Peek()
{
return _baseReader.Peek();
}
public int Position
{
get { return _position; }
}
}
}

View File

@@ -1,3 +1,5 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/Environment/Filtering/ExcludeCoverageFilters/=_002A_002ETests_003B_002A_003B_002A_003B_002A/@EntryIndexedValue">True</s:Boolean>
<s:String x:Key="/Default/Environment/Hierarchy/Build/BuildTool/CustomBuildToolPath/@EntryValue">/usr/share/dotnet/sdk/7.0.107/MSBuild.dll</s:String>
<s:Int64 x:Key="/Default/Environment/Hierarchy/Build/BuildTool/MsbuildVersion/@EntryValue">4294967293</s:Int64></wpf:ResourceDictionary>
<s:Int64 x:Key="/Default/Environment/Hierarchy/Build/BuildTool/MsbuildVersion/@EntryValue">4294967293</s:Int64>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Avalonia/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View File

@@ -1,7 +1,7 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="CsvView.App"
RequestedThemeVariant="Default">
RequestedThemeVariant="Dark">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
<Application.Styles>

View File

@@ -4,7 +4,7 @@ using Avalonia.Markup.Xaml;
namespace CsvView;
public partial class App : Application
public class App : Application
{
public override void Initialize()
{

View File

@@ -1,14 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationManifest>app.manifest</ApplicationManifest>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.0.2"/>
<PackageReference Include="Avalonia.Desktop" Version="11.0.2"/>
@@ -17,8 +16,7 @@
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.2"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\CsvLib\CsvLib.csproj" />
</ItemGroup>

View File

@@ -9,30 +9,34 @@
Width="800"
Height="600"
Title="CsvView">
<Grid RowDefinitions="Auto,Auto,*">
<StackPanel Orientation="Horizontal" Grid.Row="0">
<Button Name="BtnLoad" Click="BtnLoad_OnClick">...</Button>
<TextBlock Name="TxtFileName" />
</StackPanel>
<StackPanel Orientation="Horizontal" Grid.Row="1">
<Grid RowDefinitions="Auto,Auto,Auto,*" Margin="5">
<Grid ColumnDefinitions="Auto,*" Grid.Row="0" Margin="0 0 0 5">
<Button Grid.Column="0" Name="BtnLoad" Click="BtnLoad_OnClick">...</Button>
<TextBox Grid.Column="1" Name="TxtFileName" IsReadOnly="true" />
</Grid>
<Grid ColumnDefinitions="*,Auto" Grid.Row="1" Margin="0 0 0 5">
<TextBox Grid.Column="0" Name="TxtSearch" />
<Button Grid.Column="1" Name="BtnSearch" Click="BtnSearch_OnClick">🔍</Button>
</Grid>
<StackPanel Orientation="Horizontal" Grid.Row="2">
<Button Name="BtnFirst" Click="BtnFirst_OnClick">|◁</Button>
<Button Name="BtnPrevious" Click="BtnPrevious_OnClick">◁</Button>
<TextBox Name="TxtIndex" Text="{Binding Index}" TextChanged="TxtIndex_OnTextChanged" ></TextBox>
<TextBlock>/</TextBlock>
<TextBox Name="TxtMaxIndex" Text="{Binding MaxIndex}" IsEnabled="false"></TextBox>
<TextBox Name="TxtIndex" Text="{Binding Index}" TextChanged="TxtIndex_OnTextChanged"></TextBox>
<TextBlock VerticalAlignment="Center">/</TextBlock>
<TextBox Name="TxtMaxIndex" Text="{Binding MaxIndex}" IsReadOnly="true"></TextBox>
<Button Name="BtnNext" Click="BtnNext_OnClick">▷</Button>
<Button Name="BtnLast" Click="BtnLast_OnClick">▷|</Button>
</StackPanel>
<ScrollViewer Grid.Row="2" >
<StackPanel Orientation="Vertical" >
<ScrollViewer Grid.Row="3">
<StackPanel Orientation="Vertical">
<ItemsControl ItemsSource="{Binding Fields}">
<ItemsControl.DataTemplates>
<DataTemplate x:DataType="csvView:FieldViewModel">
<TextBox Text="{Binding Text}" IsEnabled="False"/>
<TextBox Text="{Binding Text}" IsReadOnly="true" />
</DataTemplate>
</ItemsControl.DataTemplates>
</ItemsControl>
</StackPanel>
</ScrollViewer>
</Grid>
</Window>
</Window>

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls;
@@ -5,6 +6,8 @@ using Avalonia.Interactivity;
using Avalonia.Platform.Storage;
using CsvLib;
// ReSharper disable UnusedParameter.Local
namespace CsvView;
public partial class MainWindow : Window
@@ -17,25 +20,37 @@ public partial class MainWindow : Window
private async void BtnLoad_OnClick(object? sender, RoutedEventArgs e)
{
TopLevel? topLevel = GetTopLevel(this);
if (topLevel == null) { return; }
IReadOnlyList<IStorageFile> files = await topLevel.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
try
{
Title = "Open CSV File",
AllowMultiple = false,
FileTypeFilter = new List<FilePickerFileType>
TopLevel? topLevel = GetTopLevel(this);
if (topLevel == null) { return; }
IReadOnlyList<IStorageFile> files = await topLevel.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
{
new("CSV Files") { Patterns = new[] { "*.csv" } },
new("Any File") { Patterns = new[] { "*" } },
},
});
Title = "Open CSV File",
AllowMultiple = false,
FileTypeFilter = new List<FilePickerFileType>
{
new("CSV Files") { Patterns = ["*.csv",], },
new("Any File") { Patterns = ["*",], },
},
});
if (files.Count <= 0) { return; }
if (files.Count <= 0) { return; }
LoadFile(files[0].Path.LocalPath);
LoadFile(files[0].Path.LocalPath);
}
catch (Exception)
{
// ignored
}
}
private void BtnSearch_OnClick(object? sender, RoutedEventArgs e)
{
Search(TxtSearch.Text);
}
private void BtnFirst_OnClick(object? sender, RoutedEventArgs e)
{
RenderReg(0);
@@ -64,15 +79,15 @@ public partial class MainWindow : Window
private string _loadedFile = string.Empty;
private long _currentReg;
private int _totalRegs;
private List<long> _index = new();
private List<long> _index = [];
private void LoadFile(string fileName)
{
// TODO: Loading animation
_loadedFile = fileName;
TxtFileName.Text = fileName;
CsvIndexer csvIndexer = new();
CsvFieldIndexer csvIndexer = new();
csvIndexer.LoadIndexOfFile(_loadedFile);
_index = csvIndexer.Index;
_totalRegs = _index.Count - 1;
@@ -80,21 +95,24 @@ public partial class MainWindow : Window
RenderReg(0);
}
private MainWindowViewModel Index_LoadReg(int idx, int maxIndex)
private void Search(string? textToSearch)
{
CsvParser csvParser = new();
csvParser.ParseFile(_loadedFile, _index[idx], 1);
MainWindowViewModel viewModel = new()
{
Index = idx,
MaxIndex = maxIndex,
Fields = csvParser.Data[0].Select(f => new FieldViewModel { Text = f, }).ToList(),
};
return viewModel;
if (textToSearch == null) { return; }
// TODO: Loading animation
CsvFieldIndexer csvIndexer = new();
csvIndexer.LoadIndexOfFile(_loadedFile);
List<long> newIndexes = csvIndexer.SearchFile(_loadedFile, textToSearch);
_index = newIndexes;
_totalRegs = _index.Count - 1;
RenderReg(0, forceLoad: true);
}
bool _rendering;
private void RenderReg(long currentReg)
private bool _rendering;
private void RenderReg(long currentReg, bool forceLoad = false)
{
if (_rendering) { return; }
_rendering = true;
@@ -104,11 +122,11 @@ public partial class MainWindow : Window
_currentReg = -1;
BtnFirst.IsEnabled = false;
BtnPrevious.IsEnabled = false;
TxtIndex.IsEnabled = false;
TxtIndex.IsReadOnly = true;
BtnNext.IsEnabled = false;
BtnLast.IsEnabled = false;
DataContext = new MainWindowViewModel { Index = 0, Fields = new(), };
DataContext = new MainWindowViewModel { Index = 0, Fields = [], };
_rendering = false;
return;
}
@@ -128,11 +146,11 @@ public partial class MainWindow : Window
BtnFirst.IsEnabled = (first == false);
BtnPrevious.IsEnabled = (first == false);
TxtIndex.IsEnabled = true;
TxtIndex.IsReadOnly = false;
BtnNext.IsEnabled = (last == false);
BtnLast.IsEnabled = (last == false);
if (_currentReg == currentReg)
if (_currentReg == currentReg && forceLoad == false)
{
_rendering = false;
return;
@@ -153,7 +171,6 @@ public partial class MainWindow : Window
_rendering = false;
}
}
public class FieldViewModel

View File

@@ -3,7 +3,7 @@ using System;
namespace CsvView;
class Program
static class Program
{
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
@@ -13,7 +13,7 @@ class Program
.StartWithClassicDesktopLifetime(args);
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
private static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.WithInterFont()

View File

@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2014-2015 Valeriano Alfonso Rodriguez
Copyright (c) 2014-2023 Valeriano Alfonso Rodriguez
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -15,7 +15,7 @@ CSV file viewer, for use with large files.
The MIT License (MIT)
Copyright (c) 2014-2015 Valeriano Alfonso Rodriguez
Copyright (c) 2014-2023 Valeriano Alfonso Rodriguez
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal