1 Commits
issues ... Main

Author SHA1 Message Date
a63f297a61 Ignore .issues directory 2025-05-11 17:36:49 +02:00
38 changed files with 1733 additions and 60 deletions

34
.editorconfig Normal file
View File

@@ -0,0 +1,34 @@
[*]
# Microsoft .NET properties
csharp_new_line_before_members_in_object_initializers = false
csharp_preferred_modifier_order = private, protected, public, internal, file, new, override, abstract, virtual, sealed, readonly, static, extern, unsafe, volatile, async, required:suggestion
csharp_preserve_single_line_blocks = true
csharp_style_var_elsewhere = false:suggestion
csharp_style_var_for_built_in_types = false:suggestion
csharp_style_var_when_type_is_apparent = false:suggestion
# ReSharper properties
resharper_accessor_owner_body = accessors_with_block_body
resharper_align_multiline_argument = true
resharper_align_multiline_binary_expressions_chain = false
resharper_blank_lines_after_block_statements = 0
resharper_blank_lines_around_single_line_property = 1
resharper_braces_for_for = required
resharper_braces_for_ifelse = not_required
resharper_braces_for_using = not_required
resharper_braces_redundant = false
resharper_csharp_blank_lines_around_invocable = 0
resharper_csharp_insert_final_newline = true
resharper_csharp_max_line_length = 198
resharper_csharp_remove_blank_lines_near_braces_in_declarations = false
resharper_default_internal_modifier = implicit
resharper_instance_members_qualify_declared_in =
resharper_keep_existing_enum_arrangement = false
resharper_parentheses_non_obvious_operations = none, multiplicative, additive, arithmetic, shift, bitwise_and, bitwise_exclusive_or, bitwise_inclusive_or, bitwise
resharper_parentheses_redundancy_style = remove
resharper_place_accessorholder_attribute_on_same_line = false
resharper_trailing_comma_in_multiline_lists = true
resharper_wrap_chained_binary_expressions = chop_if_long
resharper_wrap_object_and_collection_initializer_style = chop_always

30
.gitignore vendored Normal file
View File

@@ -0,0 +1,30 @@
#ignorar miniaturas creadas por windows
Thumbs.db
#Ignorar archivos construidos por Visual Studio
*.obj
*.exe
*.pdb
*.user
*.aps
*.pch
*.vspscc
*_i.c
*_p.c
*.ncb
*.suo
*.tlb
*.tlh
*.bak
*.cache
*.ilk
*.log
[Bb]in
[Dd]ebug*/
*.lib
*.sbr
obj/
[Rr]elease*/
_ReSharper*/
/.issues/

13
.idea/.idea.CsvView/.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,13 @@
# Default ignored files
/shelf/
/workspace.xml
# Rider ignored files
/projectSettingsUpdater.xml
/.idea.CsvView.iml
/contentModel.xml
/modules.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

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>

4
.idea/.idea.CsvView/.idea/encodings.xml generated Normal file
View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
</project>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>

6
.idea/.idea.CsvView/.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</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

@@ -0,0 +1,344 @@
using System.Text;
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]);
}
[Fact]
public void GenerateIndex__TwoLinesOfPainText__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(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]);
Assert.Equal(10, indexer.FieldIndex[0][1]);
Assert.Equal(2, indexer.FieldIndex[1].Count);
Assert.Equal(12, indexer.FieldIndex[1][0]);
Assert.Equal(22, indexer.FieldIndex[1][1]);
}
[Fact]
public void GenerateIndex__TwoLinesOfQuotedText__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(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]);
Assert.Equal(11, indexer.FieldIndex[0][1]);
Assert.Equal(2, indexer.FieldIndex[1].Count);
Assert.Equal(15, indexer.FieldIndex[1][0]);
Assert.Equal(25, indexer.FieldIndex[1][1]);
}
[Fact]
public void GenerateIndex__TwoLinesWithTwoQuotedColumns__TwoRowsTwoFields()
{
// --- 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(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]);
Assert.Equal(5, indexer.FieldIndex[0][1]);
Assert.Equal(9, indexer.FieldIndex[0][2]);
Assert.Equal(13, indexer.FieldIndex[0][3]);
Assert.Equal(4, indexer.FieldIndex[1].Count);
Assert.Equal(17, indexer.FieldIndex[1][0]);
Assert.Equal(21, indexer.FieldIndex[1][1]);
Assert.Equal(25, indexer.FieldIndex[1][2]);
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

@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>CvsLib.Tests</RootNamespace>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\CsvLib\CsvLib.csproj" />
</ItemGroup>
</Project>

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
}

2
CsvLib.Tests/Usings.cs Normal file
View File

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

View File

@@ -0,0 +1,52 @@
using System.IO;
using System.Text;
namespace CsvLib;
public class BufferedTextReader : TextReader
{
private readonly TextReader _baseReader;
private readonly StringBuilder _sbBuffer = new();
private readonly Encoding _currentEncoding = Encoding.Default;
public BufferedTextReader(TextReader baseReader)
{
_baseReader = baseReader;
if (baseReader is StreamReader streamReader)
{
_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;
}
}

365
CsvLib/CsvFieldIndexer.cs Normal file
View File

@@ -0,0 +1,365 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace CsvLib;
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 = '\\')
{
_separator = separator;
_quoteChar = quoteChar;
_escapeChar = escapeChar;
}
#endregion Life cycle
#region Properties
private List<long> _index = new();
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++)
{
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++;
}
}
}
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++)
{
char c = line[i];
if (c == _separator && _insideString == false)
{
if (fieldStartPosition != null && fieldEndPosition != 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++;
long absolutePosition = lineOffset + i + unicodeDelta;
fieldStartPosition ??= absolutePosition;
fieldEndPosition = absolutePosition;
}
else if ((c == '\n' || c == '\r') && _insideString == false)
{
break;
}
else
{
if (c > 127)
{
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
}

9
CsvLib/CsvLib.csproj Normal file
View File

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

104
CsvLib/CsvParser.cs Normal file
View File

@@ -0,0 +1,104 @@
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace CsvLib;
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 = '\\')
{
_separator = separator;
_quoteChar = quoteChar;
_escapeChar = escapeChar;
}
private List<List<string>> _data = new();
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++)
{
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
{
_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();
}
}

42
CsvView.sln Normal file
View File

@@ -0,0 +1,42 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CsvLib", "CsvLib\CsvLib.csproj", "{EB0FDB60-8B9D-401C-85A8-4CF4105D5063}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CsvLib.Tests", "CsvLib.Tests\CsvLib.Tests.csproj", "{EC5C84D8-1CDE-4AED-9C16-6C4086A20893}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CsvView", "CsvView\CsvView.csproj", "{65A0A7DA-5884-4DFE-8223-C8F5DBD881A8}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Misc", "Misc", "{94D4A247-9453-45F4-8552-0D106801C9F0}"
ProjectSection(SolutionItems) = preProject
README.md = README.md
LICENSE.txt = LICENSE.txt
.editorconfig = .editorconfig
.gitignore = .gitignore
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{EB0FDB60-8B9D-401C-85A8-4CF4105D5063}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EB0FDB60-8B9D-401C-85A8-4CF4105D5063}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EB0FDB60-8B9D-401C-85A8-4CF4105D5063}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EB0FDB60-8B9D-401C-85A8-4CF4105D5063}.Release|Any CPU.Build.0 = Release|Any CPU
{EC5C84D8-1CDE-4AED-9C16-6C4086A20893}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EC5C84D8-1CDE-4AED-9C16-6C4086A20893}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EC5C84D8-1CDE-4AED-9C16-6C4086A20893}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EC5C84D8-1CDE-4AED-9C16-6C4086A20893}.Release|Any CPU.Build.0 = Release|Any CPU
{65A0A7DA-5884-4DFE-8223-C8F5DBD881A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{65A0A7DA-5884-4DFE-8223-C8F5DBD881A8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{65A0A7DA-5884-4DFE-8223-C8F5DBD881A8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{65A0A7DA-5884-4DFE-8223-C8F5DBD881A8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

5
CsvView.sln.DotSettings Normal file
View File

@@ -0,0 +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>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Avalonia/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

10
CsvView/App.axaml Normal file
View File

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

23
CsvView/App.axaml.cs Normal file
View File

@@ -0,0 +1,23 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
namespace CsvView;
public class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindow();
}
base.OnFrameworkInitializationCompleted();
}
}

23
CsvView/CsvView.csproj Normal file
View File

@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<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"/>
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.2"/>
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.0.2"/>
<!--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>
</Project>

42
CsvView/MainWindow.axaml Normal file
View File

@@ -0,0 +1,42 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:csvView="clr-namespace:CsvView"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="600"
x:Class="CsvView.MainWindow"
x:DataType="csvView:MainWindowViewModel"
Width="800"
Height="600"
Title="CsvView">
<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 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="3">
<StackPanel Orientation="Vertical">
<ItemsControl ItemsSource="{Binding Fields}">
<ItemsControl.DataTemplates>
<DataTemplate x:DataType="csvView:FieldViewModel">
<TextBox Text="{Binding Text}" IsReadOnly="true" />
</DataTemplate>
</ItemsControl.DataTemplates>
</ItemsControl>
</StackPanel>
</ScrollViewer>
</Grid>
</Window>

188
CsvView/MainWindow.axaml.cs Normal file
View File

@@ -0,0 +1,188 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Platform.Storage;
using CsvLib;
// ReSharper disable UnusedParameter.Local
namespace CsvView;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
RenderReg(0);
}
private async void BtnLoad_OnClick(object? sender, RoutedEventArgs e)
{
try
{
TopLevel? topLevel = GetTopLevel(this);
if (topLevel == null) { return; }
IReadOnlyList<IStorageFile> files = await topLevel.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
{
Title = "Open CSV File",
AllowMultiple = false,
FileTypeFilter = new List<FilePickerFileType>
{
new("CSV Files") { Patterns = ["*.csv",], },
new("Any File") { Patterns = ["*",], },
},
});
if (files.Count <= 0) { return; }
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);
}
private void BtnPrevious_OnClick(object? sender, RoutedEventArgs e)
{
RenderReg(_currentReg - 1);
}
private void TxtIndex_OnTextChanged(object? sender, TextChangedEventArgs e)
{
RenderReg(int.TryParse(TxtIndex.Text, out int newReg) ? newReg : _currentReg);
}
private void BtnNext_OnClick(object? sender, RoutedEventArgs e)
{
RenderReg(_currentReg + 1);
}
private void BtnLast_OnClick(object? sender, RoutedEventArgs e)
{
RenderReg(_totalRegs - 1);
}
private string _loadedFile = string.Empty;
private long _currentReg;
private int _totalRegs;
private List<long> _index = [];
private void LoadFile(string fileName)
{
// TODO: Loading animation
_loadedFile = fileName;
TxtFileName.Text = fileName;
CsvFieldIndexer csvIndexer = new();
csvIndexer.LoadIndexOfFile(_loadedFile);
_index = csvIndexer.Index;
_totalRegs = _index.Count - 1;
RenderReg(0);
}
private void Search(string? textToSearch)
{
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);
}
private bool _rendering;
private void RenderReg(long currentReg, bool forceLoad = false)
{
if (_rendering) { return; }
_rendering = true;
if (_index.Count <= 0)
{
_currentReg = -1;
BtnFirst.IsEnabled = false;
BtnPrevious.IsEnabled = false;
TxtIndex.IsReadOnly = true;
BtnNext.IsEnabled = false;
BtnLast.IsEnabled = false;
DataContext = new MainWindowViewModel { Index = 0, Fields = [], };
_rendering = false;
return;
}
bool first = false;
bool last = false;
if (currentReg <= 0)
{
currentReg = 0;
first = true;
}
if (currentReg >= (_totalRegs - 1))
{
currentReg = _totalRegs - 1;
last = true;
}
BtnFirst.IsEnabled = (first == false);
BtnPrevious.IsEnabled = (first == false);
TxtIndex.IsReadOnly = false;
BtnNext.IsEnabled = (last == false);
BtnLast.IsEnabled = (last == false);
if (_currentReg == currentReg && forceLoad == false)
{
_rendering = false;
return;
}
_currentReg = currentReg;
CsvParser csvParser = new();
csvParser.ParseFile(_loadedFile, _index[(int)currentReg], 1);
MainWindowViewModel viewModel = new()
{
Index = (int)currentReg,
MaxIndex = _totalRegs,
Fields = csvParser.Data[0].Select(f => new FieldViewModel { Text = f, }).ToList(),
};
DataContext = viewModel;
_rendering = false;
}
}
public class FieldViewModel
{
public string Text { get; set; } = string.Empty;
}
public class MainWindowViewModel
{
public int? Index { get; set; }
public int? MaxIndex { get; set; }
public List<FieldViewModel>? Fields { get; set; }
}

21
CsvView/Program.cs Normal file
View File

@@ -0,0 +1,21 @@
using Avalonia;
using System;
namespace CsvView;
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
// yet and stuff might break.
[STAThread]
public static void Main(string[] args) => BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
// Avalonia configuration, don't remove; also used by visual designer.
private static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.WithInterFont()
.LogToTrace();
}

18
CsvView/app.manifest Normal file
View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<!-- This manifest is used on Windows only.
Don't remove it as it might cause problems with window transparency and embeded controls.
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
<assemblyIdentity version="1.0.0.0" name="CsvView.Desktop"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
</assembly>

21
LICENSE.txt Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
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
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

36
README.md Normal file
View File

@@ -0,0 +1,36 @@
# CsvView
CSV file viewer, for use with large files.
## Contributing
1. Fork it!
2. Create your feature branch: `git checkout -b my-new-feature`
3. Commit your changes: `git commit -am 'Add some feature'`
4. Push to the branch: `git push origin my-new-feature`
5. Submit a pull request :D
## Credits
* Valeriano Alfonso Rodriguez.
## License
The MIT License (MIT)
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
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

View File

@@ -1,10 +0,0 @@
id: 1
title: Portar a Dotnet y GtkSharp
state: closed
tags: enhancement
----
Creo que mejor opción es AvaloniaUI
https://avaloniaui.net/

View File

@@ -1,6 +0,0 @@
id: 2
title: Implementar indexado de campos
state: closed
tags: enhancement
`CvsIndexer` implementa un indexado de registros. Para poder realizar búsquedas de forma eficiente, seria bueno tener también un indexado de campos.

View File

@@ -1,6 +0,0 @@
id: 3
title: Implementar busquedas
state: closed
tags: enhancement

View File

@@ -1,6 +0,0 @@
id: 4
title: Genera indices con offsets incorrectos, cuando hay caracteres unicode
state: closed
tags: bug

View File

@@ -1,8 +0,0 @@
id: 5
title: Escapado de comillas dobles
state: open
tags: bug
Escapado de comillas dobles.
Este se hace duplicando. p.e:
`001,"Texto ""Texto entrecomillado"""`

View File

@@ -1,6 +0,0 @@
id: 6
title: Resaltar el texto buscado
state: open
tags: enhancement

View File

@@ -1,6 +0,0 @@
id: 7
title: Animacion de carga o barra de progreso
state: open
tags: enhancement

View File

@@ -1,6 +0,0 @@
id: 8
title: Permitir indicar que la primera fila es la cabecera
state: open
tags: enhancement

View File

@@ -1,6 +0,0 @@
id: 9
title: Comparar CsvView con Tabmega
state: open
tags: enhancement
https://tabmega.com/