using System.Text; using System.Xml.Linq; using ExcelDataReader; namespace PrepStep; /// /// Converte il file "Distinta Base" esportato dal gestionale (SpreadsheetML /// XML con estensione .xls/.xml, oppure .xls/.xlsx binario vero) nelle sole /// colonne Componente/Descrizione in CSV. /// static class DistintaBaseImporter { private static readonly XNamespace Ss = "urn:schemas-microsoft-com:office:spreadsheet"; private const string NomeFoglio = "Distinta Base"; // Il gestionale esporta la distinta come "DBP__.xls": // il foglio elenca solo i componenti SOTTO l'assieme, non l'assieme generale in sé, quindi // va aggiunto a parte al CSV leggendo questo codice dal nome del file di input. private static readonly System.Text.RegularExpressions.Regex NomeFileDistintaRegex = new(@"^DBP_(?\d+)_\d+$", System.Text.RegularExpressions.RegexOptions.Compiled); static DistintaBaseImporter() { Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance); } public static (string PercorsoCsv, List<(string Componente, string Descrizione)> Righe) ConvertiInCsv( string percorsoInput, string? cartellaOutput = null) { var righeGrezze = EImmagineSpreadsheetMl(percorsoInput) ? LeggiRigheSpreadsheetMl(percorsoInput) : LeggiRigheExcelBinario(percorsoInput); var righe = righeGrezze.Skip(1) // salta l'intestazione .Where(r => r.Componente.Length > 0 || r.Descrizione.Length > 0) .ToList(); var matchGenerale = NomeFileDistintaRegex.Match(Path.GetFileNameWithoutExtension(percorsoInput)); if (matchGenerale.Success) { string codiceGenerale = matchGenerale.Groups["codice"].Value; if (!righe.Any(r => string.Equals(r.Componente, codiceGenerale, StringComparison.OrdinalIgnoreCase))) righe.Insert(0, (codiceGenerale, "Assieme generale")); } var percorsoCsv = Path.Combine( cartellaOutput ?? Path.GetDirectoryName(Path.GetFullPath(percorsoInput))!, Path.GetFileNameWithoutExtension(percorsoInput) + ".csv"); using var writer = new StreamWriter(percorsoCsv, false, Encoding.UTF8); writer.WriteLine("Componente;Descrizione"); foreach (var (componente, descrizione) in righe) { writer.WriteLine($"{EscapeCsv(componente)};{EscapeCsv(descrizione)}"); } return (percorsoCsv, righe); } /// I file XML "SpreadsheetML" iniziano con l'intestazione XML testuale; /// i file .xls/.xlsx binari veri iniziano invece con una firma binaria (OLE2 o ZIP). private static bool EImmagineSpreadsheetMl(string percorso) { Span primiByte = stackalloc byte[5]; using var stream = File.OpenRead(percorso); var letti = stream.Read(primiByte); return letti >= 5 && Encoding.ASCII.GetString(primiByte) == " LeggiRigheSpreadsheetMl(string percorsoInput) { var doc = XDocument.Load(percorsoInput); var foglio = doc.Descendants(Ss + "Worksheet") .FirstOrDefault(w => (string?)w.Attribute(Ss + "Name") == NomeFoglio) ?? throw new InvalidDataException($"Foglio '{NomeFoglio}' non trovato nel file."); var righeXml = foglio.Descendants(Ss + "Row").ToList(); if (righeXml.Count == 0) throw new InvalidDataException($"Il foglio '{NomeFoglio}' non contiene righe."); return righeXml.Select(LeggiColonneBeCSpreadsheetMl).ToList(); } private static (string Componente, string Descrizione) LeggiColonneBeCSpreadsheetMl(XElement riga) { string b = "", c = ""; int colonna = 0; foreach (var cella in riga.Elements(Ss + "Cell")) { // ss:Index salta le celle vuote iniziali: senza attributo la colonna è quella precedente + 1 var indice = cella.Attribute(Ss + "Index"); colonna = indice != null ? int.Parse(indice.Value) : colonna + 1; if (colonna == 2) b = cella.Element(Ss + "Data")?.Value ?? ""; else if (colonna == 3) c = cella.Element(Ss + "Data")?.Value ?? ""; } return (b, c); } private static List<(string Componente, string Descrizione)> LeggiRigheExcelBinario(string percorsoInput) { using var stream = File.Open(percorsoInput, FileMode.Open, FileAccess.Read, FileShare.Read); using var reader = ExcelReaderFactory.CreateReader(stream); var dataSet = reader.AsDataSet(); var tabella = dataSet.Tables[NomeFoglio] ?? throw new InvalidDataException($"Foglio '{NomeFoglio}' non trovato nel file."); var righe = new List<(string Componente, string Descrizione)>(); foreach (System.Data.DataRow riga in tabella.Rows) { var b = riga.Table.Columns.Count > 1 ? riga[1]?.ToString() ?? "" : ""; var c = riga.Table.Columns.Count > 2 ? riga[2]?.ToString() ?? "" : ""; righe.Add((b, c)); } return righe; } private static string EscapeCsv(string valore) { if (valore.Contains(';') || valore.Contains('"') || valore.Contains('\n')) return $"\"{valore.Replace("\"", "\"\"")}\""; return valore; } }