diff --git a/.gitignore b/.gitignore
index 87c9f54..461d4c3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,3 +19,9 @@ publish/
# Stato locale degli strumenti agentici
.claude/
+
+# File Excel/XML della Distinta Base (esportati dal gestionale) e il CSV generato
+/*.xls
+/*.xlsx
+/*.xls.xml
+/*.csv
diff --git a/CadSearchGui/CadSearchGui.csproj b/CadSearchGui/CadSearchGui.csproj
index 4093c4e..77840f6 100644
--- a/CadSearchGui/CadSearchGui.csproj
+++ b/CadSearchGui/CadSearchGui.csproj
@@ -10,6 +10,8 @@
+
+
\ No newline at end of file
diff --git a/CadSearchGui/DistintaBaseImporter.cs b/CadSearchGui/DistintaBaseImporter.cs
new file mode 100644
index 0000000..cb33d1d
--- /dev/null
+++ b/CadSearchGui/DistintaBaseImporter.cs
@@ -0,0 +1,109 @@
+using System.Text;
+using System.Xml.Linq;
+using ExcelDataReader;
+
+namespace CadSearchGui;
+
+///
+/// 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";
+
+ static DistintaBaseImporter()
+ {
+ Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);
+ }
+
+ public static (string PercorsoCsv, List<(string Componente, string Descrizione)> Righe) ConvertiInCsv(string percorsoInput)
+ {
+ 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 percorsoCsv = Path.Combine(
+ 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;
+ }
+}
diff --git a/CadSearchGui/Form1.cs b/CadSearchGui/Form1.cs
index 00638ad..05345c2 100644
--- a/CadSearchGui/Form1.cs
+++ b/CadSearchGui/Form1.cs
@@ -15,6 +15,8 @@ public partial class Form1 : Form
private readonly DataGridView _grid = new();
private readonly Label _lblStatus = new();
private readonly Label _lblDb = new();
+ private readonly DataGridView _gridImport = new();
+ private readonly Label _lblImportStatus = new();
private const string OpzionePrtAsm = "Solo prt/asm (predefinito)";
private const string OpzioneTuttiTipi = "Tutti i tipi";
@@ -120,9 +122,96 @@ public partial class Form1 : Form
_lblStatus.Text = "Pronto.";
statusPanel.Controls.Add(_lblStatus);
- Controls.Add(_grid);
- Controls.Add(statusPanel);
- Controls.Add(topPanel);
+ var tabRicerca = new TabPage("Ricerca CAD");
+ tabRicerca.Controls.Add(_grid);
+ tabRicerca.Controls.Add(statusPanel);
+ tabRicerca.Controls.Add(topPanel);
+
+ var tabs = new TabControl { Dock = DockStyle.Fill };
+ tabs.TabPages.Add(BuildTabImportaDistinta());
+ tabs.TabPages.Add(tabRicerca);
+
+ Controls.Add(tabs);
+ }
+
+ private TabPage BuildTabImportaDistinta()
+ {
+ var tab = new TabPage("Importa Distinta Base") { AllowDrop = true };
+
+ var lblIstruzioni = new Label
+ {
+ Dock = DockStyle.Top,
+ Height = 60,
+ Padding = new Padding(8),
+ Text = "Trascina qui il file .xls/.xml esportato dal gestionale (foglio 'Distinta "
+ + "Base'), oppure scegli il file col pulsante sotto. Verrà generato un CSV con "
+ + "le colonne Componente e Descrizione, con anteprima qui sotto.",
+ };
+
+ var btnSfoglia = new Button { Text = "Scegli file...", Dock = DockStyle.Top, Height = 32 };
+ btnSfoglia.Click += (_, _) => ScegliFileDistinta();
+
+ _lblImportStatus.Dock = DockStyle.Top;
+ _lblImportStatus.Height = 24;
+ _lblImportStatus.Padding = new Padding(8, 4, 8, 4);
+ _lblImportStatus.ForeColor = SystemColors.GrayText;
+
+ _gridImport.Dock = DockStyle.Fill;
+ _gridImport.ReadOnly = true;
+ _gridImport.AllowUserToAddRows = false;
+ _gridImport.AllowUserToDeleteRows = false;
+ _gridImport.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
+ _gridImport.Columns.Add("componente", "Componente");
+ _gridImport.Columns.Add("descrizione", "Descrizione");
+
+ tab.Controls.Add(_gridImport);
+ tab.Controls.Add(_lblImportStatus);
+ tab.Controls.Add(btnSfoglia);
+ tab.Controls.Add(lblIstruzioni);
+
+ tab.DragEnter += (_, e) =>
+ e.Effect = e.Data?.GetDataPresent(DataFormats.FileDrop) == true
+ ? DragDropEffects.Copy
+ : DragDropEffects.None;
+ tab.DragDrop += (_, e) =>
+ {
+ if (e.Data?.GetData(DataFormats.FileDrop) is string[] { Length: > 0 } file)
+ ImportaDistinta(file[0]);
+ };
+
+ return tab;
+ }
+
+ private void ScegliFileDistinta()
+ {
+ using var dialog = new OpenFileDialog
+ {
+ Filter = "File distinta base (*.xls;*.xml)|*.xls;*.xml|Tutti i file|*.*",
+ Title = "Seleziona il file Distinta Base",
+ };
+ if (dialog.ShowDialog(this) == DialogResult.OK)
+ ImportaDistinta(dialog.FileName);
+ }
+
+ private void ImportaDistinta(string percorso)
+ {
+ try
+ {
+ var (csv, righe) = DistintaBaseImporter.ConvertiInCsv(percorso);
+ _gridImport.Rows.Clear();
+ foreach (var (componente, descrizione) in righe)
+ {
+ _gridImport.Rows.Add(componente, descrizione);
+ }
+ _lblImportStatus.ForeColor = Color.DarkGreen;
+ _lblImportStatus.Text = $"CSV generato ({righe.Count} righe): {csv}";
+ }
+ catch (Exception ex)
+ {
+ _gridImport.Rows.Clear();
+ _lblImportStatus.ForeColor = Color.DarkRed;
+ _lblImportStatus.Text = $"Errore durante la conversione: {ex.Message}";
+ }
}
private void InizializzaDatabase()