Add explicit working folder; rename project from CadSearchGui to PrepStep
Working folder (phase 1): CSV/raw/step/step_finali all used to live split across two different implicit locations (CSV in the "repo root" found by walking up for CLAUDE.md, raw/step next to the input distinta file) with neither shown to the user. That repo-root lookup also silently breaks once the app runs as a standalone published exe on a machine with no repo/ CLAUDE.md at all. Replaced both with one explicit "cartella di lavoro", auto-suggested (last used, remembered the same way as the DB path) and changeable from phase 1, with no dependency on any repo marker file. Rename: CadSearchGui was the working name from before the app had a product identity; renamed folders, csproj files, namespace and the AppData settings folder to PrepStep throughout, matching the exe name already in use. Product-facing name stays "Cevolani StepPrep". Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6726dd2216
commit
13798ad47c
24 files changed
+211
-129
No files matched your search
@@ -0,0 +1,124 @@
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
using ExcelDataReader;
|
||||
|
||||
namespace PrepStep;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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_<codice assieme generale>_<timestamp>.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_(?<codice>\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);
|
||||
}
|
||||
|
||||
/// <summary>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).</summary>
|
||||
private static bool EImmagineSpreadsheetMl(string percorso)
|
||||
{
|
||||
Span<byte> primiByte = stackalloc byte[5];
|
||||
using var stream = File.OpenRead(percorso);
|
||||
var letti = stream.Read(primiByte);
|
||||
return letti >= 5 && Encoding.ASCII.GetString(primiByte) == "<?xml";
|
||||
}
|
||||
|
||||
private static List<(string Componente, string Descrizione)> 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;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user