Refine .asm component extraction: batch DB queries, wider suffix regex, placement check

- Batch the DB lookup per file instead of per code in scripts/ExtractCreoStrings.cs:
  files_ultima_versione is an unmaterialized view, so each query rescanned the whole
  files table (~230ms) - batching cuts a multi-minute BFS to seconds.
- Widen the code regex to accept a revision letter and a "_..." suffix together
  (e.g. "9284200366A_C01"), and purely numeric "_NNNN" suffixes (e.g. dimensional
  variants like "2702220182_8188"), not just letter+digits.
- Require bare digit-only candidates to also appear inside a real
  "Componente ID N (...)"/"Component id N (...)" placement, filtering out
  incidental references to a containing assembly's own code.
- scripts/ExtractCreoStrings.cs now reports only the direct references of the given
  .asm (one level, matching Creo's own tree for that file) instead of recursing into
  sub-assemblies.
- Port the regex and placement-check refinements to the production
  AssemblyComponentScanner.cs used by the app's "Trova componenti referenziati" and
  "Verifica completezza" phases.
- Document the new false-positive categories (parent/container self-reference,
  suppressed/removed components with residual placement, placement missing
  suffix/bracket detail) in docs/formato-asm.md, generalized beyond the specific
  assemblies used to find them.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
davideandClaude Sonnet 5 committed 2026-09-17 14:59:23 +02:00
1 parent e6de563f60
commit 9660c4883d
4 files changed
+185 -50

No files matched your search

@@ -14,8 +14,10 @@ public class AssemblyComponentScannerTests
[Fact] [Fact]
public void Scarta_codici_con_una_sola_occorrenza() public void Scarta_codici_con_una_sola_occorrenza()
{ {
// "11112222" compare 2 volte (riferimento vero), "99998888" una sola (rumore). // "11112222" compare 2 volte con un vero placement (riferimento vero), "99998888"
string percorso = ScriviAsmFinto("blah 11112222 blah 99998888 blah 11112222 fine"); // una sola volta senza placement (rumore).
string percorso = ScriviAsmFinto(
"blah Componente ID 1 (11112222.PRT) blah 99998888 blah 11112222 fine");
try try
{ {
var codici = AssemblyComponentScanner.EstraiCodiciReferenziati(percorso); var codici = AssemblyComponentScanner.EstraiCodiciReferenziati(percorso);
@@ -25,6 +27,20 @@ public class AssemblyComponentScannerTests
finally { File.Delete(percorso); } finally { File.Delete(percorso); }
} }
[Fact]
public void Scarta_codice_ripetuto_senza_un_vero_placement()
{
// Riferimento al nome di un altro assieme (es. il padre che lo contiene): ripetuto
// più volte ma mai dentro un placement "Componente ID N (...)" - va scartato.
string percorso = ScriviAsmFinto("padre 33334444 assieme 33334444_D200 33334444");
try
{
var codici = AssemblyComponentScanner.EstraiCodiciReferenziati(percorso);
Assert.DoesNotContain("33334444", codici);
}
finally { File.Delete(percorso); }
}
[Fact] [Fact]
public void Riconosce_sotto_assiemi_con_suffisso_stile_D100() public void Riconosce_sotto_assiemi_con_suffisso_stile_D100()
{ {
+24 -2
View File
@@ -25,11 +25,27 @@ namespace CadSearchGui;
/// binario (es. "R6--_-_U6592&lt;UNI6592&gt;") è la singola istanza, non un file separato /// binario (es. "R6--_-_U6592&lt;UNI6592&gt;") è la singola istanza, non un file separato
/// su disco - il file reale indicizzato è il generico "uni6592.prt.&lt;versione&gt;", quindi /// su disco - il file reale indicizzato è il generico "uni6592.prt.&lt;versione&gt;", quindi
/// va estratto solo il contenuto tra "&lt;" e "&gt;". Vedi docs/formato-asm.md. /// va estratto solo il contenuto tra "&lt;" e "&gt;". Vedi docs/formato-asm.md.
///
/// Lettera di revisione e suffisso "_..." possono comparire insieme sullo stesso codice
/// (es. "9284200366A_C01"), e il suffisso dopo "_" può essere puramente numerico (es.
/// "2702220182_8188", una variante dimensionale, non lettera+cifre come "_C01"/"_D100").
///
/// Un codice "nudo" (senza suffisso "_..." e senza bracket UNI) ripetuto più volte non
/// basta: può trattarsi del riferimento al nome di un altro assieme che lo contiene (non
/// noto a priori, quindi non escludibile per confronto diretto come il proprio codice) o
/// di un residuo di feature soppresse/superate (vedi docs/formato-asm.md). Per questi si
/// richiede anche un vero placement "Componente ID N (&lt;codice&gt;...)"/
/// "Component id N (&lt;codice&gt;...)" (la stringa cambia con la localizzazione Creo del
/// file). Non si applica alle varianti con suffisso o bracket UNI: in alcuni file il
/// placement riporta solo il nome istanza/generico senza il dettaglio del suffisso o del
/// bracket, che vive altrove nei metadati - lì resta necessaria la sola euristica delle
/// occorrenze ripetute.
/// </summary> /// </summary>
static class AssemblyComponentScanner static class AssemblyComponentScanner
{ {
private static readonly Regex CodiceRegex = new( private static readonly Regex CodiceRegex = new(
@"(?<!ModelView#)\b\d{8,12}(?:_[A-Za-z]\d{2,4}|[A-Za-z]{0,2})\b|(?<=<)UNI\d+(?=>)", RegexOptions.Compiled); @"(?<!ModelView#)\b\d{8,12}[A-Za-z]{0,2}(?:_[A-Za-z]?\d{2,4})?\b|(?<=<)UNI\d+(?=>)", RegexOptions.Compiled);
private static readonly Regex CodiceNudoRegex = new(@"^\d{8,12}[A-Za-z]{0,2}$", RegexOptions.Compiled);
private const int OccorrenzeMinime = 2; private const int OccorrenzeMinime = 2;
public static HashSet<string> EstraiCodiciReferenziati(string percorsoAsm) public static HashSet<string> EstraiCodiciReferenziati(string percorsoAsm)
@@ -40,6 +56,12 @@ static class AssemblyComponentScanner
{ {
occorrenze[m.Value] = occorrenze.GetValueOrDefault(m.Value) + 1; occorrenze[m.Value] = occorrenze.GetValueOrDefault(m.Value) + 1;
} }
return occorrenze.Where(kv => kv.Value >= OccorrenzeMinime).Select(kv => kv.Key).ToHashSet(); var candidati = occorrenze.Where(kv => kv.Value >= OccorrenzeMinime).Select(kv => kv.Key).ToHashSet();
bool HaPlacement(string codice) =>
Regex.IsMatch(testo, $@"Component(?:e)? id \d+ \([^()]*{Regex.Escape(codice)}[^()]*\)", RegexOptions.IgnoreCase);
candidati.RemoveWhere(c => CodiceNudoRegex.IsMatch(c) && !HaPlacement(c));
return candidati;
} }
} }
+77 -5
View File
@@ -41,6 +41,17 @@ regex: \b\d{8,12}(?:_[A-Za-z]\d{2,4}|[A-Za-z]{0,2})\b
occorrenze: >= 2 occorrenze: >= 2
``` ```
`scripts/ExtractCreoStrings.cs` ha una versione più raffinata della regex e un filtro
aggiuntivo (vedi sotto), non ancora portati in `AssemblyComponentScanner.cs`:
```
regex: \b\d{8,12}[A-Za-z]{0,2}(?:_[A-Za-z]?\d{2,4})?\b
```
(lettera di revisione e suffisso "_..." possono comparire anche insieme, es.
`9284200366A_C01`; il suffisso dopo "_" può essere puramente numerico, es.
`2702220182_8188`, non solo lettera+cifre come `_C01`/`_D100`.)
## Falsi positivi noti ## Falsi positivi noti
**ID di viste salvate.** Il pattern `ModelView#<numero>` (es. **ID di viste salvate.** Il pattern `ModelView#<numero>` (es.
@@ -54,6 +65,26 @@ volte nei metadati (`CODICE`, `part_table`, `STAMPIGLIATURA`, ecc.) e supera
quindi la soglia di occorrenze. Va escluso esplicitamente confrontandolo con quindi la soglia di occorrenze. Va escluso esplicitamente confrontandolo con
il codice derivato dal nome del file in ingresso (`nomefile.split('.')[0]`). il codice derivato dal nome del file in ingresso (`nomefile.split('.')[0]`).
**Il codice dell'assieme padre (o di qualunque altro assieme che lo
contiene).** Variante dello stesso problema: un sotto-assieme può contenere
anche il codice dell'assieme che lo referenzia (riferimento al nome
sessione/file salvato nei metadati), che supera la soglia di occorrenze pur
non essendo un componente. A differenza del proprio codice, non è noto a
priori: dipende da dove/come il file è stato aperto o da chi lo referenzia,
quindi non si può escludere per confronto diretto come per il caso
precedente. La regola generale che discrimina questo caso: un componente
vero compare **sempre** anche dentro un vero placement
`Componente ID N (<codice>...)`/`Component id N (<codice>...)` (la stringa
cambia con la localizzazione Creo con cui il file è stato salvato - osservate
sia italiana sia inglese); un riferimento incidentale al nome di un altro
assieme, anche se ripetuto più volte, non compare mai in quel contesto.
`scripts/ExtractCreoStrings.cs` applica questa regola ai codici "nudi" (senza
suffisso `_...` e senza bracket UNI - vedi sotto perché limitata a questi):
un codice ripetuto ma mai trovato dentro un placement viene scartato. Questo
NON copre i casi fantasma/soppressi sotto, che hanno un placement
sintatticamente identico a uno vero e restano quindi indistinguibili a
livello testuale.
**Riferimenti fantasma a codici superati/rinominati.** Caso osservato: **Riferimenti fantasma a codici superati/rinominati.** Caso osservato:
`9244207061.asm` contiene un riferimento a `9044207061` (stesso numero, prima `9244207061.asm` contiene un riferimento a `9044207061` (stesso numero, prima
di una rinumerazione) con la stessa identica struttura sintattica di un di una rinumerazione) con la stessa identica struttura sintattica di un
@@ -70,6 +101,39 @@ riesce a discriminarlo in modo affidabile senza rischiare di scartare
componenti veri con pattern simili altrove. Se ricompare, l'unica verifica componenti veri con pattern simili altrove. Se ricompare, l'unica verifica
sicura resta l'apertura manuale in Creo del sotto-assieme sospetto. sicura resta l'apertura manuale in Creo del sotto-assieme sospetto.
**Componenti soppressi/rimossi con placement residuo.** Stessa natura del
caso precedente: capita che un codice compaia con un vero
`Componente ID N (<codice>.PRT)`/`Component id N (<codice>.PRT)` - con un ID
numerico nello stesso intervallo di quelli dei componenti realmente visibili
nell'albero Creo, quindi non distinguibile per posizione o formato - ma il
codice non compare nell'albero/BOM che Creo mostra per quell'assieme.
Osservato su più assiemi in questa sessione (`9044207061` dentro
`9244207061.asm`, e diversi codici dentro `9284208188_D200.asm`). Ipotesi più
probabile: feature soppresse, cancellate o superate in una revisione
successiva, il cui placement resta comunque nella storia binaria del file
(Creo non riscrive da zero il file a ogni salvataggio, accumula stato). Nella
pratica, quando compaiono, sono un numero ridotto di codici extra rispetto al
reale (falsi positivi, non falsi negativi): la BOM generata resta un
sovrainsieme sicuro dei componenti veri, mai un sottoinsieme. Nessuna
euristica testuale può escluderli con certezza; se serve un elenco esatto
l'unica verifica è aprire l'assieme in Creo e controllare a mano quali ID
sono effettivamente presenti/attivi.
**Placement senza il dettaglio del suffisso/bracket.** Il testo dentro
`Componente ID N (...)` a volte riporta solo il nome generico/istanza, senza
il suffisso di variante dimensionale o il bracket family-table, che compaiono
invece solo altrove nei metadati dello stesso file (non vicino al placement).
Osservato in due forme sullo stesso assieme (`9284208188_D200.asm`):
`Component id 82 (R8--_-_U6592.PRT)` (nessun `<UNI6592>` nel placement, pure
presente nel file) e `Component id 150 (2702220182.PRT)` (nessun `_8188`
nel placement, mentre la variante dimensionale reale `2702220182_8188`
compare solo come stringa ripetuta altrove). Per questo il requisito del
placement esplicito (caso sopra) si applica solo ai codici "nudi" (senza
suffisso `_...` e senza bracket UNI): per le varianti con suffisso o family
table il placement può mancare del dettaglio, quindi resta necessaria la
sola euristica delle occorrenze ripetute - meno precisa, ma l'unica
applicabile senza rischiare falsi negativi su casi come questi.
## Viti/dadi/rosette normalizzati (family table UNI) ## Viti/dadi/rosette normalizzati (family table UNI)
I componenti di minuteria standard (viti, dadi, rosette UNI) sono definiti in I componenti di minuteria standard (viti, dadi, rosette UNI) sono definiti in
@@ -98,7 +162,8 @@ referenziati" dell'app).
## Risoluzione ricorsiva dell'albero ## Risoluzione ricorsiva dell'albero
Un `.asm` referenzia solo i propri sotto-componenti diretti, non l'intero Un `.asm` referenzia solo i propri sotto-componenti diretti, non l'intero
albero annidato. Per ricostruire la distinta base completa serve una BFS: albero annidato. Per ricostruire la distinta base completa (non solo il primo
livello) serve una BFS:
1. Estrarre i codici candidati dal file corrente (euristica sopra). 1. Estrarre i codici candidati dal file corrente (euristica sopra).
2. Per ogni codice, cercarlo nell'indice `cad_index.db` 2. Per ogni codice, cercarlo nell'indice `cad_index.db`
@@ -107,11 +172,18 @@ albero annidato. Per ricostruire la distinta base completa serve una BFS:
3. Un codice non trovato nell'indice va scartato/segnalato: o è rumore 3. Un codice non trovato nell'indice va scartato/segnalato: o è rumore
binario, o è un componente non ancora indicizzato. binario, o è un componente non ancora indicizzato.
Stessa logica in tre punti del codice, con scopi diversi: Stessa logica di estrazione candidati in due punti del codice, con scopi
diversi:
- [`AssemblyComponentScanner.EstraiCodiciReferenziati`](../CadSearchGui/AssemblyComponentScanner.cs) — solo estrazione dei candidati da un file. - [`AssemblyComponentScanner.EstraiCodiciReferenziati`](../CadSearchGui/AssemblyComponentScanner.cs) — solo estrazione dei candidati da un file, usata dalla BFS in [`Form1.RisolviAlberoComponenti`](../CadSearchGui/Form1.cs) (fase "Trova componenti referenziati") e [`Form1.AssiemeCompleto`](../CadSearchGui/Form1.cs) (fase "Verifica completezza").
- [`Form1.AssiemeCompleto`](../CadSearchGui/Form1.cs) — BFS per verificare se un assieme è completo nell'indice (fase "Verifica completezza"). - [`scripts/ExtractCreoStrings.cs`](../scripts/ExtractCreoStrings.cs) — estrazione standalone da riga di comando, ma **un solo livello**: riporta solo i riferimenti diretti del file passato (come li mostra Creo per quell'assieme), senza scendere ricorsivamente nei sotto-assiemi. Usato per confrontare l'euristica contro l'albero Creo di un singolo file durante il reverse engineering; non genera una distinta base completa multi-livello.
- [`scripts/ExtractCreoStrings.cs`](../scripts/ExtractCreoStrings.cs) — stesso BFS, standalone, per generare un `BOM.txt` da riga di comando.
Nota: le query contro `files_ultima_versione` filtrano solo per
`(cartella_radice, codice, tipo_file)` - se lo stesso codice esiste in più
cartelle radice sulla share (duplicati/copie storiche), va sempre ordinato
per `data_modifica DESC` e tenuta solo la riga più recente per tipo, altrimenti
si rischia di risolvere un codice contro una copia diversa (e potenzialmente
diversa nel contenuto) da quella realmente referenziata.
## Strumento di analisi manuale ## Strumento di analisi manuale
+66 -41
View File
@@ -1,13 +1,12 @@
#:package Microsoft.Data.Sqlite@10.0.12 #:package Microsoft.Data.Sqlite@10.0.12
// dotnet run scripts/ExtractCreoStrings.cs -- <file.asm> [cad_index.db] [output.txt] // dotnet run scripts/ExtractCreoStrings.cs -- <file.asm> [cad_index.db] [output.txt]
// //
// Estrae dal file .asm i codici dei componenti referenziati (stessa euristica di // Estrae dal file .asm i codici dei componenti referenziati DIRETTAMENTE (un solo
// CadSearchGui/AssemblyComponentScanner.cs: i codici veri compaiono più volte nel // livello, come li mostra Creo per quell'assieme - nessuna espansione ricorsiva nei
// file, il rumore binario quasi sempre una sola volta; gli ID interni // sotto-assiemi). Stessa euristica di CadSearchGui/AssemblyComponentScanner.cs: i codici
// "ModelView#<numero>" sono esclusi via lookbehind - vedi scripts/Analyze.cs) e poi // veri compaiono più volte nel file, il rumore binario quasi sempre una sola volta; gli
// risolve ricorsivamente i sotto-assiemi cercandoli nell'indice cad_index.db, con lo // ID interni "ModelView#<numero>" sono esclusi via lookbehind - vedi scripts/Analyze.cs.
// stesso BFS di Form1.AssiemeCompleto: un assieme può referenziare solo i propri // Ogni codice trovato viene validato contro l'indice cad_index.db.
// sotto-assiemi diretti, non l'intero albero, quindi va camminato livello per livello.
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Microsoft.Data.Sqlite; using Microsoft.Data.Sqlite;
@@ -27,8 +26,13 @@ string outPath = args.Length > 2 ? args[2] : "BOM.txt";
// il nome della singola istanza (riga della family table, non un file separato su disco), // il nome della singola istanza (riga della family table, non un file separato su disco),
// il file reale indicizzato è il generico "uni6592.prt.<versione>" - va quindi estratto // il file reale indicizzato è il generico "uni6592.prt.<versione>" - va quindi estratto
// solo il contenuto tra "<" e ">". // solo il contenuto tra "<" e ">".
// Terza alternativa: varianti dimensionali con suffisso puramente numerico (es. cinghie
// "2702220182_8188", dove "8188" è una misura, non una lettera di revisione/sotto-assieme
// come "_C01"/"_D100") - la lettera dopo "_" è quindi opzionale, non obbligatoria. La
// lettera di revisione e il suffisso "_" possono anche comparire insieme sullo stesso
// codice (es. "9284200366A_C01", componente di un sotto-assieme già di per sé revisionato).
var codiceRegex = new Regex( var codiceRegex = new Regex(
@"(?<!ModelView#)\b\d{8,12}(?:_[A-Za-z]\d{2,4}|[A-Za-z]{0,2})\b|(?<=<)UNI\d+(?=>)", @"(?<!ModelView#)\b\d{8,12}[A-Za-z]{0,2}(?:_[A-Za-z]?\d{2,4})?\b|(?<=<)UNI\d+(?=>)",
RegexOptions.Compiled); RegexOptions.Compiled);
const int occorrenzeMinime = 2; const int occorrenzeMinime = 2;
@@ -38,57 +42,78 @@ HashSet<string> EstraiCodiciReferenziati(string percorsoAsm)
var occorrenze = new Dictionary<string, int>(); var occorrenze = new Dictionary<string, int>();
foreach (Match m in codiceRegex.Matches(testo)) foreach (Match m in codiceRegex.Matches(testo))
occorrenze[m.Value] = occorrenze.GetValueOrDefault(m.Value) + 1; occorrenze[m.Value] = occorrenze.GetValueOrDefault(m.Value) + 1;
return occorrenze.Where(kv => kv.Value >= occorrenzeMinime).Select(kv => kv.Key).ToHashSet(); var candidati = occorrenze.Where(kv => kv.Value >= occorrenzeMinime).Select(kv => kv.Key).ToHashSet();
// Ripetersi >=2 volte nel file non basta per un codice "nudo" (senza suffisso "_..."):
// riferimenti al proprio assieme padre (nome sessione/file) o a codici superati/
// rinominati (caso "fantasma", docs/formato-asm.md) ripetono comunque il codice più
// volte pur non essendo mai un componente vero. Per questi si richiede un vero
// placement "Componente ID N (<codice>...)". "Componente ID"/"Component id": la
// stringa cambia con la localizzazione Creo con cui il file è stato salvato (osservate
// sia italiana sia inglese). Non si applica alle varianti con suffisso "_..." né alle
// istanze family table UNI: in alcuni file il placement riporta solo il nome
// istanza/generico senza il dettaglio del suffisso o del bracket "<...>", che vive
// altrove nei metadati - lì l'occorrenza ripetuta resta l'unico segnale disponibile.
bool HaPlacement(string codice) =>
Regex.IsMatch(testo, $@"Component(?:e)? id \d+ \([^()]*{Regex.Escape(codice)}[^()]*\)", RegexOptions.IgnoreCase);
candidati.RemoveWhere(c => Regex.IsMatch(c, @"^\d{8,12}[A-Za-z]{0,2}$") && !HaPlacement(c));
return candidati;
} }
using var connection = new SqliteConnection($"Data Source={dbPath};Mode=ReadOnly"); using var connection = new SqliteConnection($"Data Source={dbPath};Mode=ReadOnly");
connection.Open(); connection.Open();
using var command = connection.CreateCommand();
command.CommandText = "SELECT tipo_file, percorso_completo FROM files_ultima_versione "
+ "WHERE LOWER(codice) = LOWER(@codice) AND LOWER(tipo_file) IN ('prt', 'asm')";
var pCodice = command.Parameters.Add("@codice", SqliteType.Text);
List<(string Tipo, string Percorso)> CercaComponenti(string codice) // "files_ultima_versione" è una VIEW con ROW_NUMBER() OVER (...), che SQLite non
// materializza: ogni query la ricalcola sull'intera tabella "files" (~230ms anche per un
// solo codice, su ~300k righe). Batchare tutti i codici trovati in un file in un'unica
// query paga quel costo una volta per file invece che una volta per codice - decisivo
// sugli assiemi con centinaia di sotto-componenti referenziati.
Dictionary<string, List<(string Tipo, string Percorso)>> CercaComponenti(IReadOnlyCollection<string> codici)
{ {
pCodice.Value = codice; var risultato = new Dictionary<string, List<(string, string)>>(StringComparer.OrdinalIgnoreCase);
var risultato = new List<(string, string)>(); foreach (var codice in codici)
var tipiVisti = new HashSet<string>(); risultato[codice] = new List<(string, string)>();
if (codici.Count == 0)
return risultato;
using var command = connection.CreateCommand();
var nomiParametri = codici.Select((_, i) => $"@c{i}").ToList();
command.CommandText = "SELECT codice, tipo_file, percorso_completo FROM files_ultima_versione "
+ $"WHERE LOWER(codice) IN ({string.Join(",", nomiParametri)}) AND LOWER(tipo_file) IN ('prt', 'asm')";
int i = 0;
foreach (var codice in codici)
command.Parameters.AddWithValue(nomiParametri[i++], codice.ToLowerInvariant());
var tipiVisti = new Dictionary<string, HashSet<string>>(StringComparer.OrdinalIgnoreCase);
using var reader = command.ExecuteReader(); using var reader = command.ExecuteReader();
while (reader.Read()) while (reader.Read())
{ {
string tipo = reader.GetString(0).ToLowerInvariant(); string codiceDb = reader.GetString(0);
if (tipiVisti.Add(tipo)) string tipo = reader.GetString(1).ToLowerInvariant();
risultato.Add((tipo, reader.GetString(1))); var visti = tipiVisti.TryGetValue(codiceDb, out var v) ? v : tipiVisti[codiceDb] = new HashSet<string>();
if (visti.Add(tipo))
risultato[codiceDb].Add((tipo, reader.GetString(2)));
} }
return risultato; return risultato;
} }
var codiciVisti = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { Path.GetFileName(asmIniziale).Split('.')[0] }; // Solo i riferimenti diretti del file indicato (un livello), come li mostra Creo per
var daVisitare = new Queue<string>(); // quell'assieme - nessuna espansione ricorsiva nei sotto-assiemi.
daVisitare.Enqueue(asmIniziale); var codiceProprio = Path.GetFileName(asmIniziale).Split('.')[0];
var componentiTrovati = new SortedSet<string>(StringComparer.OrdinalIgnoreCase); var componentiTrovati = new SortedSet<string>(StringComparer.OrdinalIgnoreCase);
var nonTrovati = new List<string>(); var nonTrovati = new List<string>();
while (daVisitare.Count > 0) var candidati = EstraiCodiciReferenziati(asmIniziale)
.Where(c => !string.Equals(c, codiceProprio, StringComparison.OrdinalIgnoreCase))
.ToList();
var trovatiPerCodice = CercaComponenti(candidati);
foreach (var codice in candidati)
{ {
var file = daVisitare.Dequeue(); if (trovatiPerCodice[codice].Count == 0)
foreach (var codice in EstraiCodiciReferenziati(file)) nonTrovati.Add(codice);
{ else
if (!codiciVisti.Add(codice))
continue;
var componenti = CercaComponenti(codice);
if (componenti.Count == 0)
{
nonTrovati.Add(codice);
continue;
}
componentiTrovati.Add(codice); componentiTrovati.Add(codice);
var percorsoAsm = componenti.FirstOrDefault(c => c.Tipo == "asm").Percorso;
if (percorsoAsm is not null)
daVisitare.Enqueue(percorsoAsm);
}
} }
File.WriteAllLines(outPath, componentiTrovati); File.WriteAllLines(outPath, componentiTrovati);