using System.Text; using System.Text.RegularExpressions; namespace CadSearchGui; /// /// Estrae i codici componente referenziati dentro un file .asm Pro-E/Creo. /// Il formato è per lo più binario, ma i codici dei sotto-componenti compaiono /// come stringhe ASCII leggibili al suo interno: si scansiona il file come testo /// cercando pattern che assomigliano a un codice (stesso stile usato nei nomi file, /// vedi Indexer.FilenameRegex) e si lascia poi al chiamante il compito di verificarli /// contro l'indice DB, che scarta automaticamente i falsi positivi (numeri che non /// corrispondono a nessun codice reale). /// /// Verificato su un caso reale confrontando con un report BOM generato da Creo: i /// riferimenti veri compaiono più volte nel file (una volta per feature/placement), /// mentre il rumore binario casuale che assomiglia a un codice compare quasi sempre /// una sola volta. Si scartano quindi i candidati con una sola occorrenza. /// /// Falso positivo noto ed escluso: gli ID interni di viste salvate, nel formato /// "ModelView#<numero>", hanno la stessa forma di un codice (8-12 cifre) e /// compaiono più volte come un riferimento vero. Vedi docs/formato-asm.md. /// /// Viti/dadi/rosette normalizzati UNI (e cuscinetti CUSCI_...) sono family table Creo: il /// nome che compare nel binario (es. "R6--_-_U6592<UNI6592>", /// "2510060303<CUSCI_ACBB7>") è la singola istanza, non un file separato su disco - il /// file reale indicizzato è il generico "uni6592.prt.<versione>"/ /// "cusci_acbb7.prt.<versione>", quindi 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, in una delle quattro forme osservate nel binario: /// "Componente ID N (<codice>...)"/"Component id N (<codice>...)" (la stringa /// cambia con la localizzazione Creo del file); il codice ripetuto due volte di seguito /// separato da un singolo byte NUL ("<codice>\0<codice>\0", osservato per /// componenti di family table non-UNI, es. cuscinetti "CUSCI_..."); il campo /// "family_name\0<codice>\0" (osservato una sola volta per file, su un sotto-assieme /// referenziato come family table generica invece che con un placement testuale esplicito); /// oppure il codice seguito immediatamente da un bracket "<codice><GENERICO>" - /// l'istanza di una family table non-UNI vista nel nome (es. "2510060303<CUSCI_ACBB7>", /// un cuscinetto): il codice dell'istanza non è mai indicizzato a sé (solo il generico tra /// "<" e ">" lo è), ma questo segnale lo fa comunque sopravvivere fino alla ricerca nel /// DB invece di scomparire in silenzio come "nudo senza placement" - lì verrà comunque /// scartato come "non in indice", ma in modo tracciabile. 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. /// static class AssemblyComponentScanner { private static readonly Regex CodiceRegex = new( @"(?)", RegexOptions.Compiled); private static readonly Regex CodiceNudoRegex = new(@"^\d{8,12}[A-Za-z]{0,2}$", RegexOptions.Compiled); private const int OccorrenzeMinime = 2; /// Codice radice (parte prima del primo "_") di un codice componente. Un /// assieme con suffisso "_D.../_C..." referenzia sempre al suo interno anche l'assieme /// generale a cui appartiene (codice radice, senza suffisso) - riferimento fantasma /// verso l'alto, non un componente vero (vedi docs/formato-asm.md). Chi fa una /// risoluzione ricorsiva dell'albero deve seminare l'insieme dei codici già visitati /// anche con la radice del codice di partenza, non solo col codice stesso, altrimenti /// risale fino all'assieme generale e riporta dentro l'intero albero del macchinario. public static string CodiceRadice(string codice) => codice.Split('_')[0]; public static HashSet EstraiCodiciReferenziati(string percorsoAsm) { var testo = Encoding.Latin1.GetString(File.ReadAllBytes(percorsoAsm)); var occorrenze = new Dictionary(); foreach (Match m in CodiceRegex.Matches(testo)) { occorrenze[m.Value] = occorrenze.GetValueOrDefault(m.Value) + 1; } 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) || testo.Contains(codice + "\0" + codice + "\0") || testo.Contains("family_name\0" + codice + "\0") || testo.Contains(codice + "<"); candidati.RemoveWhere(c => CodiceNudoRegex.IsMatch(c) && !HaPlacement(c)); return candidati; } }