CSVAdapter - Jednoduchá práca s CSV súbormi v .NET
Dnešný článok bude tak trochu netradičný. Nebudeme sa venovať nejakej technológii alebo nejakému mini projektu, ale predstavíme si knižnicu CSVAdapter z našej dielne.
V dnešnom netradičnom článku si predstavíme knižnicu CSVAdapter, ktorá vznikla s cieľom zjednodušiť prácu s CSV súbormi. Knižnica je prístupná na nasledujúcej adrese > link <
- Jednotlivé riadky tabuľky (prípadne hlavička) sú skutočne oddelené "novým riadkom" (CRLF, LF, CR podľa platformy)
- Bunky v riadku sú oddelené oddelovačom
- Hodnoty v bunkách môžu (ale nemusia) byť uzavreté v úvodzovkách ", v prípade ak samotná hodnota obsahuje úvodzovky, je ich nutné zdvojiť a hodnotu uzavrieť do úvodzoviek. ("Toto je ""parádna"" hodnota").
- Ak hodnota v bunke obsahuje znak, ktorý je zároveň aj oddeľovač je nutné hodnoty uzavrieť do úvodzoviek.
Diskusia
CSV súbory
Na úvod by sa asi patrilo napísať niečo aj o samotných CSV súboroch. CSV súbor je v podstate textový súbor, ktorý sa používa na prenos tabuľkových informácii medzi rôznymi aplikáciami / riešeniami.Naneštastie formát CSV nemá jednotný formát, ktorý by každý rešpektoval. Používajú sa rozličné formáty textového zápisu hodnôt dátumu, čísel s desatinnou čiarkou, dokonca aj oddeľovača hodnôt. Aj keď názov formátu hovorí o "hodnotách oddelených čiarkou" (Comma-Separated Values), nie je to pravidlom. Ako oddeľovače sa okrem čiarky ,, používajú aj bodkočiarka ; alebo dokonca aj tabulátor.V princípe však platí nasledovné:- Jednotlivé riadky tabuľky (prípadne hlavička) sú skutočne oddelené "novým riadkom" (CRLF, LF, CR podľa platformy)
- Bunky v riadku sú oddelené oddelovačom
- Hodnoty v bunkách môžu (ale nemusia) byť uzavreté v úvodzovkách ", v prípade ak samotná hodnota obsahuje úvodzovky, je ich nutné zdvojiť a hodnotu uzavrieť do úvodzoviek. ("Toto je ""parádna"" hodnota").
- Ak hodnota v bunke obsahuje znak, ktorý je zároveň aj oddeľovač je nutné hodnoty uzavrieť do úvodzoviek.
Knižnica CSVAdapter
Ako už názov napovedá, tak knižnica CSVAdapter slúži ako adaptér medzi CSV a Vašou aplikáciou. Postará sa o načítanie / uloženie dát s/do CSV súboru, ktoré Vám aicky ponúkne ako .NETovú DataTable. Ako zdroj dát je možné použiť CSV súbor ale aj priamo textovú premennú obsahujúcu dáta. Zároveň je možné dáta načítať s aickou konverziou na požadované typy hodnôt, alebo ich načítať bez konverzie (všetky stĺpce v tabuľke budú typu String). Jednotlivé možnosti si teraz ukážeme na príkladoch.Vytvorenie a nastavenie adaptéra
Do projektu pridáme referenciu na knižnicu CSVAdapter.dll, ktorú sme si pred tým stiahli zo stránky MST Bytes (> link <). Následne si vytvoríme objekt Adapter typu CSVTableAdapter (namespace MSTBytes.Tools.CSVAdapter), v ktorom si nastavíme potrebné náležitosti:Zdrojový kód:
using System;
using System.Data;
using System.Globalization;
using MSTBytes.Tools.CSVAdapter;... CSVTableAdapter Adapter = new CSVTableAdapter(1);
Adapter.Delimiter = ","; //Oddelovac hodnot
Adapter.HeaderRows = 1; //Pocet riadkov, ktore su hlavickou (je zadany uz v konstruktore) //Nastavenie pre potreby konverzie hodnot
Adapter.DateFormat = "yyyyMMdd"; //Format datumu
Adapter.NumberFormat = new CultureInfo("en-US").NumberFormat; //Format cisel
Adapter.AllowDBNull = false; //Nepovolit pouzivanie DBNull pri prazdnych bunkach (namiesto neho pouzit "Empty" hodnoty nizsie) //Nepovinne - Predvolene hodnoty pre prazdne bunky (ak je AllowDBNull true)
Adapter.EmptyDateTimeValue = DateTime.MinValue;
Adapter.EmptyStringValue = "";
Adapter.EmptyDoubleValue = 0.0;
Adapter.EmptyIntValue = 0;
...
Konštruktor má nepovinný parameter HeaderRows, ktorý hovorí o počte riadkov v súbore, ktoré majú byť považované za hlavičkové. Zvyčajne je hlavička v jednom riadku, no existujú aj výnimky (napríklad ak druhý riadok slúži ako krátky popis stĺpca, alebo ako cudzojazyčný názov stĺpca).Ďalej sme si nastavili oddeľovač vo vlastnosti Delimiter a počet hlavičkových riadkov v HeaderRows (čo v tomto prípade nie je potrebné, keďže sme tak už spravili v hlavičke. Uvádzam len pre úplnosť).Ak budeme používať aickú konverziu hodnôt, je potrebné nastaviť vlastnosti DateFormat a NumberFormat, kde uvedieme formáty, v ktorých by zdrojový / cieľový CSV mal byť uložený. Vlastnosť AllowDBNull riadi spôsob, akým sa parser vysporiada s prázdnymi údajmi v súbore. Ak je nastavená na true, interpretuje prázdnu hodnotu ako DBNull, ak je false, tak siahne po "prázdnej hodnote" z nastavení. Samotné "prázdne hodnoty" pre jednotlivé typy hodnôt, môžeme zadefinovať cez vlastnosti EmptyDateTimeValue, EmptyStringValue, EmptyDoubleValue a EmptyIntValue. using System.Data;
using System.Globalization;
using MSTBytes.Tools.CSVAdapter;... CSVTableAdapter Adapter = new CSVTableAdapter(1);
Adapter.Delimiter = ","; //Oddelovac hodnot
Adapter.HeaderRows = 1; //Pocet riadkov, ktore su hlavickou (je zadany uz v konstruktore) //Nastavenie pre potreby konverzie hodnot
Adapter.DateFormat = "yyyyMMdd"; //Format datumu
Adapter.NumberFormat = new CultureInfo("en-US").NumberFormat; //Format cisel
Adapter.AllowDBNull = false; //Nepovolit pouzivanie DBNull pri prazdnych bunkach (namiesto neho pouzit "Empty" hodnoty nizsie) //Nepovinne - Predvolene hodnoty pre prazdne bunky (ak je AllowDBNull true)
Adapter.EmptyDateTimeValue = DateTime.MinValue;
Adapter.EmptyStringValue = "";
Adapter.EmptyDoubleValue = 0.0;
Adapter.EmptyIntValue = 0;
...
Uloženie dát do súboru
Dáta, ktoré máme obsiahnuté v tabuľke (DataTable), vieme jednoducho uložiť do CSV súboru cez metódu ToFile:Zdrojový kód:
...
DataTable Data = new DataTable();
Data.Columns.Add("Item", typeof(string));
Data.Columns.Add("Quantity", typeof(double));
Data.Columns.Add("Duedate", typeof(DateTime));
Data.Columns.Add("Price", typeof(double)); Data.Rows.Add("I001", 23, new DateTime(2021, 12, 12), 35.1);
Data.Rows.Add("I002", 22, new DateTime(2021, 12, 31), 1.5); CSVTableAdapter Adapter = new CSVTableAdapter(1);
Adapter.Delimiter = ",";
Adapter.DateFormat = "yyyyMMdd";
Adapter.ToFile(Data, "test.csv");
...
V prípade, ak sú v tabuľke hodnoty typu Double alebo DateTime, budú aicky uložené vo formáte, ktorý je v adaptéry nastavený.DataTable Data = new DataTable();
Data.Columns.Add("Item", typeof(string));
Data.Columns.Add("Quantity", typeof(double));
Data.Columns.Add("Duedate", typeof(DateTime));
Data.Columns.Add("Price", typeof(double)); Data.Rows.Add("I001", 23, new DateTime(2021, 12, 12), 35.1);
Data.Rows.Add("I002", 22, new DateTime(2021, 12, 31), 1.5); CSVTableAdapter Adapter = new CSVTableAdapter(1);
Adapter.Delimiter = ",";
Adapter.DateFormat = "yyyyMMdd";
Adapter.ToFile(Data, "test.csv");
...
Načítanie dát zo súboru s aickou konverziou
Tento spôsob načítania dát použijeme v prípade, ak je nám štruktúra známa a vieme ju zadefinovať. Vytvoríme si prázdnu tabuľku s požadovanou štruktúrou a necháme adaptér, aby ju naplnil zo zadaného súboru. Na tento účel použijeme metódu FromFile.Zdrojový kód:
... /*
* Súbor by mal obsahovať:
* Item,Quantity,DueDate,Price
* I001,23,20211212,35.1
* I002,22,20211231,1.5
*/ DataTable Data = new DataTable();
Data.Columns.Add("Item", typeof(string));
Data.Columns.Add("Quantity", typeof(double));
Data.Columns.Add("Duedate", typeof(DateTime));
Data.Columns.Add("Price", typeof(double)); CSVTableAdapter Adapter = new CSVTableAdapter(1);
Adapter.Delimiter = ",";
Adapter.DateFormat = "yyyyMMdd";
Adapter.FromFile(ref Data, "test.csv");...
* Súbor by mal obsahovať:
* Item,Quantity,DueDate,Price
* I001,23,20211212,35.1
* I002,22,20211231,1.5
*/ DataTable Data = new DataTable();
Data.Columns.Add("Item", typeof(string));
Data.Columns.Add("Quantity", typeof(double));
Data.Columns.Add("Duedate", typeof(DateTime));
Data.Columns.Add("Price", typeof(double)); CSVTableAdapter Adapter = new CSVTableAdapter(1);
Adapter.Delimiter = ",";
Adapter.DateFormat = "yyyyMMdd";
Adapter.FromFile(ref Data, "test.csv");...
Načítanie dát zo súboru s bez konverzie
V prípade, ak chceme len načítať obsah CSV súboru, bez toho, aby boli hodnoty nejako upravované, môžeme zavolať metódu FromFileRaw, ktorá načíta "surové" (Raw) dáta do tabuľky. V tomto prípade, štruktúru tabuľky vopred nedefinujeme, adaptér ju aicky vytvorí podľa hodnôt v hlavičke a všetky stĺpce budú typu String.Zdrojový kód:
... /*
* Súbor by mal obsahovať:
* Item,Quantity,DueDate,Price
* I001,23,20211212,35.1
* I002,22,20211231,1.5
*/ CSVTableAdapter Adapter = new CSVTableAdapter(1);
Adapter.Delimiter = ",";
DataTable Data = Adapter.FromFileRaw("test.csv");...
* Súbor by mal obsahovať:
* Item,Quantity,DueDate,Price
* I001,23,20211212,35.1
* I002,22,20211231,1.5
*/ CSVTableAdapter Adapter = new CSVTableAdapter(1);
Adapter.Delimiter = ",";
DataTable Data = Adapter.FromFileRaw("test.csv");...
Načítanie / uloženie dát z / do premennej
Alternatívou k práci so súbormi je možnosť pracovať s dátami v premennej typu String. Práca s nimi je rovnaká ako v predchádzajúcich príkladoch, avšak sa používajú metódy, ktoré nemajú v názve File ale String. Takže namiesto FromFile použijeme FromString.Zdrojový kód:
... string CSVData = "Item,Quantity,DueDate,Price" + Environment.NewLine
+ "I001,23,20211212,35.1" + Environment.NewLine
+ "I002,22,20211231,1.5"; DataTable Data = new DataTable();
Data.Columns.Add("Item", typeof(string));
Data.Columns.Add("Quantity", typeof(double));
Data.Columns.Add("Duedate", typeof(DateTime));
Data.Columns.Add("Price", typeof(double)); CSVTableAdapter Adapter = new CSVTableAdapter(1);
Adapter.Delimiter = ",";
Adapter.DateFormat = "yyyyMMdd";
Adapter.AllowDBNull = false;
Adapter.FromString(ref Data, CSVData);...
+ "I001,23,20211212,35.1" + Environment.NewLine
+ "I002,22,20211231,1.5"; DataTable Data = new DataTable();
Data.Columns.Add("Item", typeof(string));
Data.Columns.Add("Quantity", typeof(double));
Data.Columns.Add("Duedate", typeof(DateTime));
Data.Columns.Add("Price", typeof(double)); CSVTableAdapter Adapter = new CSVTableAdapter(1);
Adapter.Delimiter = ",";
Adapter.DateFormat = "yyyyMMdd";
Adapter.AllowDBNull = false;
Adapter.FromString(ref Data, CSVData);...
Vlastné parsovanie hodnôt
Ak by súbor, ktorý chceme načítať, obsahoval dáta v neštandardnom formáte, ktorý nevieme zadefinovať cez štandardné prostriedky, je možné pre tieto hodnoty vytvoriť vlastný parser, ktorý sa v prípade potreby použije počas parsovania vstupného súboru / reťazca. Pre tento účel ma adaptér event OnValueParsing, ktorý je vyvolaný pred každým parsovaním hodnoty. V prípade, ak obsluha eventu nechá premennú ParsedValue nezmenenú, prebehne štandardné parsovanie. V opačnom prípade sa táto hodnota použije ako výsledok parsovania.Zdrojový kód:
private static void CustomParsing_Example()
{
string CSVData = "Item,Quantity,DueDate,Price" + Environment.NewLine
+ "I001,23,1,35.1" + Environment.NewLine
+ "I002,22,2,1.5"; DataTable Data = new DataTable();
Data.Columns.Add("Item", typeof(string));
Data.Columns.Add("Quantity", typeof(double));
Data.Columns.Add("Duedate", typeof(DateTime));
Data.Columns.Add("Price", typeof(double)); CSVTableAdapter Adapter = new CSVTableAdapter(1);
Adapter.Delimiter = ",";
Adapter.DateFormat = "yyyyMMdd";
Adapter.AllowDBNull = false;
Adapter.OnValueParsing += Adapter_OnValueParsing;
Adapter.FromString(ref Data, CSVData);
}private static void Adapter_OnValueParsing(string Value, int RowIndex, int ColumnIndex, DataTable Table, ref object ParsedValue)
{
//Vlastne parsovanie stlpca Duedate
if (ColumnIndex == 2)
{
DateTime ActualDate = DateTime.Now.Date;
int Days; if (String.IsNullOrEmpty(Value))
{
ParsedValue = ActualDate;
}
else if (Int32.TryParse(Value, out Days))
{
ParsedValue = ActualDate.AddDays(Days);
}
else
{
ParsedValue = DBNull.Value;
}
}
}
V príklade sme si ukázali, ako parsovať dátumovú hodnotu, ktorá je zapísaná ako počet dní od dátumu načítania dát.{
string CSVData = "Item,Quantity,DueDate,Price" + Environment.NewLine
+ "I001,23,1,35.1" + Environment.NewLine
+ "I002,22,2,1.5"; DataTable Data = new DataTable();
Data.Columns.Add("Item", typeof(string));
Data.Columns.Add("Quantity", typeof(double));
Data.Columns.Add("Duedate", typeof(DateTime));
Data.Columns.Add("Price", typeof(double)); CSVTableAdapter Adapter = new CSVTableAdapter(1);
Adapter.Delimiter = ",";
Adapter.DateFormat = "yyyyMMdd";
Adapter.AllowDBNull = false;
Adapter.OnValueParsing += Adapter_OnValueParsing;
Adapter.FromString(ref Data, CSVData);
}private static void Adapter_OnValueParsing(string Value, int RowIndex, int ColumnIndex, DataTable Table, ref object ParsedValue)
{
//Vlastne parsovanie stlpca Duedate
if (ColumnIndex == 2)
{
DateTime ActualDate = DateTime.Now.Date;
int Days; if (String.IsNullOrEmpty(Value))
{
ParsedValue = ActualDate;
}
else if (Int32.TryParse(Value, out Days))
{
ParsedValue = ActualDate.AddDays(Days);
}
else
{
ParsedValue = DBNull.Value;
}
}
}
Vlastné formátovanie hodnôt
Rovnaký prípad neštandardného formátovania môže nastať aj pri zápise dát do súboru / reťazca. Na uplatnenie vlastného formátu hodnoty pri zápise je možné použiť event OnValueFormating, ktorý funguje obdobne ako OnValueParsing.Zdrojový kód:
private static void CustomFormating_Example()
{
DataTable Data = new DataTable();
Data.Columns.Add("Item", typeof(string));
Data.Columns.Add("Quantity", typeof(double));
Data.Columns.Add("Duedate", typeof(DateTime));
Data.Columns.Add("Price", typeof(double)); Data.Rows.Add("I001", 23, new DateTime(2021, 12, 12), 35.1);
Data.Rows.Add("I002", 22, new DateTime(2021, 12, 31), 1.5); CSVTableAdapter Adapter = new CSVTableAdapter(1);
Adapter.Delimiter = ",";
Adapter.DateFormat = "yyyyMMdd";
Adapter.OnValueFormating += Adapter_OnValueFormating;
string Result = Adapter.ToString(Data);
}private static void Adapter_OnValueFormating(object Value, int RowIndex, int ColumnIndex, DataTable Table, ref string FormatedValue)
{
//Vlastne formatovanie stlpca Duedate
if (ColumnIndex == 2)
{
DateTime dtValue = (DateTime)Value;
DateTime ActualDate = DateTime.Now.Date; TimeSpan Diff = dtValue - ActualDate;
FormatedValue = Convert.ToString(Convert.ToInt32(Math.Round(Diff.TotalDays, 0)));
}
}
{
DataTable Data = new DataTable();
Data.Columns.Add("Item", typeof(string));
Data.Columns.Add("Quantity", typeof(double));
Data.Columns.Add("Duedate", typeof(DateTime));
Data.Columns.Add("Price", typeof(double)); Data.Rows.Add("I001", 23, new DateTime(2021, 12, 12), 35.1);
Data.Rows.Add("I002", 22, new DateTime(2021, 12, 31), 1.5); CSVTableAdapter Adapter = new CSVTableAdapter(1);
Adapter.Delimiter = ",";
Adapter.DateFormat = "yyyyMMdd";
Adapter.OnValueFormating += Adapter_OnValueFormating;
string Result = Adapter.ToString(Data);
}private static void Adapter_OnValueFormating(object Value, int RowIndex, int ColumnIndex, DataTable Table, ref string FormatedValue)
{
//Vlastne formatovanie stlpca Duedate
if (ColumnIndex == 2)
{
DateTime dtValue = (DateTime)Value;
DateTime ActualDate = DateTime.Now.Date; TimeSpan Diff = dtValue - ActualDate;
FormatedValue = Convert.ToString(Convert.ToInt32(Math.Round(Diff.TotalDays, 0)));
}
}
Záverom
To je pre dnešok všetko. Ukázali sme si princíp práce s knižnicou CSVAdapter a dúfam, že boli príklady zrozumiteľné a ľahko pochopiteľné. Pri písaní tohto článku bola použitá knižnica vo verzii 0.9.0.5, kde úvodná 0 naznačuje, že je knižnica stále vo fáze vývoja a uvítame všetky vaše podnety na zlepšenie aj prípadne hlásenia chýb. Urobiť tak môžete cez kontaktný formulár na stránke MST Bytes > link <Žiadne príspevky v diskusii.
Na prispievanie do diskusie musíte byť prihlásený.