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:
1 parent
e6de563f60
commit
9660c4883d
4 files changed
+184
-49
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()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -25,11 +25,27 @@ namespace CadSearchGui;
|
|||||||
/// binario (es. "R6--_-_U6592<UNI6592>") è la singola istanza, non un file separato
|
/// binario (es. "R6--_-_U6592<UNI6592>") è la singola istanza, non un file separato
|
||||||
/// su disco - il file reale indicizzato è il generico "uni6592.prt.<versione>", quindi
|
/// su disco - il file reale indicizzato è il generico "uni6592.prt.<versione>", quindi
|
||||||
/// va estratto solo il contenuto tra "<" e ">". Vedi docs/formato-asm.md.
|
/// va estratto solo il contenuto tra "<" e ">". 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 (<codice>...)"/
|
||||||
|
/// "Component id N (<codice>...)" (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
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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))
|
||||||
var file = daVisitare.Dequeue();
|
.ToList();
|
||||||
foreach (var codice in EstraiCodiciReferenziati(file))
|
var trovatiPerCodice = CercaComponenti(candidati);
|
||||||
{
|
foreach (var codice in candidati)
|
||||||
if (!codiciVisti.Add(codice))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var componenti = CercaComponenti(codice);
|
|
||||||
if (componenti.Count == 0)
|
|
||||||
{
|
{
|
||||||
|
if (trovatiPerCodice[codice].Count == 0)
|
||||||
nonTrovati.Add(codice);
|
nonTrovati.Add(codice);
|
||||||
continue;
|
else
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
||||||
|
|||||||
Reference in new issue
Block a user