Difference between revisions of "CSharp:Accesso ai files, CSV"
From Aino Wiki
(→Mappa e Links) |
(No difference)
|
Latest revision as of 19:03, 25 November 2020
Contents
Trasformazioni
Trasformazione da CSV a DataTable
La trasformazione contraria è molto ma molto più semplice per cui illustrerò questo caso concreto su un mio adattamento di una libreria presa da Sébastien Lorion di LumenWorks nel 2006 e distribuita con la MIT license. La libreria è scaricabile qui mi raccomando un minimo di cenno a me ma soprattutto all'autore, è necessario adattare il namespace, segue un esempio di come usare la libreria col suo "punto di ingresso".
Includere le cartelle secondo la stessa struttura scompattata del CSV.rar, nell'esempio seguente ho alterato il namespace per riferirmi alla libreria per ragioni di copiright.
//... using RootNameSpaceDaAdattare.Libraries.IO.Csv; //... namespace RootNameSpaceDaAdattare.Libraries.IO { public class FileHelper { #region CSV, FIXED length I\O //quel che è qui lo riporto nel codice\metodo che è nella sezione successiva #endregion
Quindi ecco il codice col punto di ingresso alla libreria (CsvReader
):
#region CSV, FIXED length I\O /// <summary> /// Dato un file CSV ne restituisce il DataTable i cui campi\colonne sono quelli CSV ovvero separati da un carattere nel file. /// </summary> /// <param name="fileFullPath">Percorso fisico del file comprensivo di nome file</param> /// <param name="strFileEncoding">Encoding col quale LEGGERE il file su FileSystem, se NULL lo determina automaticamente. Valori previsti per System.Text.Encoding</param> /// <param name="hasHeaders">Se ha o meno una intestazione</param> /// <param name="delimiter">Carattere delimitatore che separa ciascun campo</param> /// <param name="quote4EveryField">Carattere contenitore di ogni campo (NON confondere col separatore di campo), SE NON PREVISTO PASSARE ' ' oppure null /// E' FONDAMENTALE, altrimenti l'algoritmo potrebbe fallire in casi limite. Se non si usa TIPIZZAZIONE dei campi PASSARE NULL</param> /// <param name="skipFirstNRow">Primi N righi da saltare eventualmente</param> /// <param name="maxRecords">Numero massimo di righi entro cui restituire l'output</param> /// <param name="showRowNumber">Se mostrare il numero di riga accanto a ciascun record come se fosse un campo previsto in prima colonna</param> /// <returns></returns> public static DataTable GetDataTableFromCSV(string fileFullPath, string strFileEncoding, bool hasHeaders, char delimiter, char? quote4EveryField, int skipFirstNRow, int maxRecords, bool showRowNumber) { if (!File.Exists(fileFullPath)) { throw new Exception(String.Format("Non trovato il file da elaborare '{0}'.", fileFullPath)); } string strRunningLOG = string.Empty; string fileName = Path.GetFileName(fileFullPath); int recordIndex = 0; int fieldCount = 0; DataTable dt = new DataTable(fileName); Encoding encodingFileInput = GetEncodingCodeFromString(strFileEncoding, fileFullPath); //Si crea eventuale colonna del progressivo (utile in fase di TEST per definire il dataTable con le info) if (showRowNumber) { dt.Columns.Add("Nr", typeof(string)); } //Verifica inconsistenze if (hasHeaders) { string strLOG = string.Empty; HeaderWithDuplicate(fileFullPath, skipFirstNRow, delimiter, ref strLOG); strRunningLOG += strLOG; } int currFieldIndex = 0; try { //Carattere contenitore di ogni campo (NON confondere col separatore di campo) //Prima il DEfault era il " ovvero: quote = '"'; char quote = (quote4EveryField == null || quote4EveryField == ' ') ? (char)0 : quote4EveryField.Value; //'"'; using (CsvReader csv = new CsvReader(new StreamReader(fileFullPath, encodingFileInput), hasHeaders, delimiter, quote)) { csv.DefaultHeaderName = "X"; fieldCount = csv.FieldCount; //Fa scattare la lettura del BUFFER !!! //Vettore di intestazione string[] headers = csv.GetFieldHeaders(); if (headers.Length == 0) { headers = new string[fieldCount]; for (int i = 0; i < fieldCount; i++) { headers[i] = "C" + (i +1); } } string columnName = string.Empty; for (int i = 0; i < fieldCount; i++) { columnName = headers[i]; dt.Columns.Add(columnName, typeof(string)); } //Eventuale salto delle prime n righe if (skipFirstNRow > 0) { while (csv.ReadNextRecord() && skipFirstNRow > 0) { skipFirstNRow--; } } //----------------------------------------------------------- // Lettura + Scrittura delle Righe\Records! //----------------------------------------------------------- fieldCount = showRowNumber ? fieldCount + 1 : fieldCount; //Si cicla sull'oggetto CSV [ogni ReadNextRecord fa AVANZARE sul rigo successivo] per ciascun rigo ovvero //per ciascun record, ogniuno di questo verrà aggiunto al dataTable ciclando ancora per ciascun valore\campo della colonna while (csv.ReadNextRecord() && (maxRecords == 0 || recordIndex < maxRecords)) { recordIndex++; DataRow dr = dt.NewRow(); currFieldIndex = 0; //Per ogni colonna for (int i = 0; i < fieldCount; i++) { if (showRowNumber && i == 0) { dr[i] = recordIndex; currFieldIndex--; } else { dr[i] = csv[currFieldIndex]; } if (csv.TranformationPatchDone) //Se è stato necessario porre delle pezze con caratteri di transizione { dr[i] = csv.RestoreFieldPatched(dr[i].ToString()); } currFieldIndex++; } dt.Rows.Add(dr); } } } catch (Exception ex) { //ATTENZIONE non ha attendibilità certificata la info: recordIndex + 1. Dall'ultimo test sembra veritiero ovvero: // recordIndex + 1 è il rigo del file dov'è l'errore string strError = String.Format("Errore in [GetDataTableFromCSV] record {0}, campo nr {1}.\r\n{2}", recordIndex + 1, currFieldIndex, ex.Message + "\r\n\r\n" + ex.StackTrace); throw new Exception(strError); } return dt; } #endregion
A cui aggiungere i seguenti metodi di servizio:
/// <summary> /// Determina l'encoding del file di cui fornisco il Path. E' un tentetivo ma non è detto che funzionio, studia documentazione. /// </summary> /// <param name="fileFullPath"></param> /// <returns></returns> public static Encoding TryDetectFileEncoding(string fileFullPath) { #region DOCUMENTATION /* You can't depend on the file having a BOM. UTF-8 doesn't require it. And non-Unicode encodings don't even have a BOM. There are, however, other ways to detect the encoding. UTF-32 BOM is 00 00 FE FF (for BE) or FF FE 00 00 (for LE). But UTF-32 is easy to detect even without a BOM. This is because the Unicode code point range is restricted to U+10FFFF, and thus UTF-32 units always have the pattern 00 {0x|10} xx xx (for BE) or xx xx {0x|10} 00 (for LE). If the data has a length that's a multiple of 4, and follows one of these patterns, you can safely assume it's UTF-32. False positives are nearly impossible due to the rarity of 00 bytes in byte-oriented encodings. US-ASCII No BOM, but you don't need one. ASCII can be easily identified by the lack of bytes in the 80-FF range. UTF-8 BOM is EF BB BF. But you can't rely on this. Lots of UTF-8 files don't have a BOM, especially if they originated on non-Windows systems. But you can safely assume that if a file validates as UTF-8, it is UTF-8. False positives are rare. Specifically, given that the data is not ASCII, the false positive rate for a 2-byte sequence is only 3.9% (1920/49152). For a 7-byte sequence, it's less than 1%. For a 12-byte sequence, it's less than 0.1%. For a 24-byte sequence, it's less than 1 in a million. UTF-16 BOM is FE FF (for BE) or FF FE (for LE). Note that the UTF-16LE BOM is found at the start of the UTF-32LE BOM, so check UTF-32 first. There may be UTF-16 files without a BOM, but it would be really hard to detect them. The only reliable way to recognize UTF-16 without a BOM is to look for surrogate pairs (D[8-B]xx D[C-F]xx), but non-BMP characters are too rarely-used to make this approach practical. XML If your file starts with the bytes 3C 3F 78 6D 6C (i.e., the ASCII characters "<?xml"), then look for an encoding=declaration. If present, then use that encoding. If absent, then assume UTF-8, which is the default XML encoding. If you need to support EBCDIC, also look for the equivalent sequence 4C 6F A7 94 93. In general, if you have a file format that contains an encoding declaration, then look for that declaration rather than trying to guess the encoding. */ #endregion // *** Use Default of Encoding.Default (Ansi CodePage) Encoding enc = Encoding.Default; // *** Detect byte order mark if any - otherwise assume default byte[] buffer = new byte[5]; FileStream file = new FileStream(fileFullPath, FileMode.Open); file.Read(buffer, 0, 5); file.Close(); if (buffer[0] == 0xef && buffer[1] == 0xbb && buffer[2] == 0xbf) { enc = Encoding.UTF8; } else if (buffer[0] == 0xfe && buffer[1] == 0xff) { enc = Encoding.Unicode; } else if (buffer[0] == 0 && buffer[1] == 0 && buffer[2] == 0xfe && buffer[3] == 0xff || buffer[0] == 0xFF && buffer[1] == 0xFE && buffer[2] == 0 && buffer[3] == 0) { enc = Encoding.UTF32; } else if (buffer[0] == 0x2b && buffer[1] == 0x2f && buffer[2] == 0x76) { enc = Encoding.UTF7; } else if (buffer[0] == 0xFE && buffer[1] == 0xFF) // 1201 unicodeFFFE Unicode (Big-Endian) { enc = Encoding.GetEncoding(1201); } else if (buffer[0] == 0xFF && buffer[1] == 0xFE || buffer[0] == 0xFE && buffer[1] == 0xFF) // 1200 utf-16 Unicode { enc = Encoding.GetEncoding(1200); } return enc; } #endregion #region Private METHODS private static Encoding GetEncodingCodeFromString(string strEncoding, string fileFullPath) { Encoding encodingCalc = Encoding.Default; if (string.IsNullOrEmpty(strEncoding)) { encodingCalc = TryDetectFileEncoding(fileFullPath); } else { switch (strEncoding) { case "ASCII": encodingCalc = Encoding.ASCII; break; case "BigEndianUnicode": encodingCalc = Encoding.BigEndianUnicode; break; case "Default": encodingCalc = Encoding.Default; break; case "Unicode": encodingCalc = Encoding.Unicode; break; case "UTF32": encodingCalc = Encoding.UTF32; break; case "UTF7": encodingCalc = Encoding.UTF7; break; case "UTF8": encodingCalc = Encoding.UTF8; break; } } return encodingCalc; } private static string PrintDr(DataRow dr) { string strOutput = string.Empty; int nrDataColumn = dr.Table.Columns.Count; for (int i = 0; i < nrDataColumn; i++) { strOutput += "'" + dr[i] + "'\t"; } return strOutput + "\r\n"; } private static bool HeaderWithDuplicate(string fileFullPath, int skipFirstNRow, char delimiter, ref string strLOG) { string lineHeader; try { StreamReader sr = new StreamReader(fileFullPath); lineHeader = sr.ReadLine(); if (skipFirstNRow > 0) { skipFirstNRow--; while (lineHeader != null && skipFirstNRow > 0) { lineHeader = sr.ReadLine(); } } sr.Close(); //Ho la riga di intestazione!!! string[] columns = lineHeader.Split(delimiter); if (columns.Length > 0) { string stringToFind = string.Empty; for (int j = 0; j < columns.Length; j++) { stringToFind = columns[j]; for (int i = j + 1; i < columns.Length; i++) { if (columns[i].IndexOf(stringToFind) >= 0) { strLOG += string.IsNullOrEmpty(strLOG) ? "Intestazione con colonne duplicate:\r\n" : string.Empty; strLOG += stringToFind + "\r\n"; break; } } } } } catch (Exception e) { strLOG += "\r\n[HeaderWithDuplicate] Exception: " + e.Message; } return !string.IsNullOrEmpty(strLOG); } /// <summary> /// Verifica se per il rigo\DataRow corrente c'è un campo contente l'informazione da riportare ai righi successivi. /// Il contenuto di questo campo puntato andrà in corrispondenza del campo\Coloumn vuoto nel gruppo di info di prodotto successive) /// </summary> /// <param name="currentDr"></param> /// <returns>Se il rigo corrente contiene il campo cercato restituisce la posizione dello stesso altrimenti -1 !</returns> private static int IsThereFieldToRepeat(DataRow currentDr, int nColumn) { int maxToQualifyFieldToRepeat = 1; int idxCurrent = -1; int nrNotEmpty = 0; for (int j = 0; j < nColumn; j++) { //Si fa una IMPORTANTE IPOTESI ovvero che i campi ripetuti siano testuali !!!!!!!!!!!!!!!!!!!!!!! if (currentDr[j] != DBNull.Value && currentDr[j].ToString() != string.Empty) { idxCurrent = j; nrNotEmpty++; } if (nrNotEmpty > maxToQualifyFieldToRepeat) { break; } } if (nrNotEmpty == 0) { return -2; //Codice per far escludere il rigo corrente in quanto totalmente vuoto! } // Funziona solo per: maxToQualifyFieldToRepeat == 1 return (nrNotEmpty == maxToQualifyFieldToRepeat) ? idxCurrent : -1; } #endregion
Trasformazione da DataTable a CSV
public static void DatatableToCsv(System.Web.HttpResponse response, DataTable dt, string fileName, string delimiter) { response.Clear(); response.ContentType = "text/csv"; response.AppendHeader("Content-Disposition", string.Format("attachment; filename={0}", fileName)); for (int i = 0; i < dt.Columns.Count; i++) { response.Write(dt.Columns[i].ColumnName); response.Write((i < dt.Columns.Count - 1) ? delimiter : Environment.NewLine); } foreach (DataRow row in dt.Rows) { for (int i = 0; i < dt.Columns.Count; i++) { response.Write(row[i].ToString()); response.Write((i < dt.Columns.Count - 1) ? delimiter : Environment.NewLine); } } response.End(); }
Mappa e Links
Visual Studio | Programmazione | DataTable | Automatismo caricamento Entità