Vai al contenuto

Come anonimizzare i dati in Excel

    COME ANONIMIZZARE DATI MICROSOFT EXCEL MARCOFILOCAMO DETTAGLIO NOMI MANTENUTI

    In questa guida vediamo come anonimizzare i dati in Excel prima di condividere un file, sostituendo i valori originali con dati sintetici ma mantenendo la struttura della tabella.

    COME ANONIMIZZARE DATI MICROSOFT EXCEL MARCOFILOCAMO PROCESSO COMPLESSIVO

    È una situazione piuttosto comune quando dobbiamo chiedere aiuto a un collega, inviare un file a un consulente oppure utilizzare strumenti come ChatGPT o Copilot senza poter condividere tutti i dati reali. Potremmo semplicemente cancellare i valori oppure sostituirli con XXX, ma in questo modo perderemmo anche informazioni utili per analizzare correttamente il file.

    Per questo ho creato SANITIZE.xlsm, un file Excel separato che legge una Tabella Excel, genera nuovi valori sintetici e crea un nuovo file .xlsx. Possiamo scegliere se anonimizzare tutte le colonne oppure soltanto quelle che contengono informazioni che non vogliamo condividere.

    Il workbook originale viene aperto in sola lettura e non viene modificato.

    Alcune specifiche sui file .xlsm, necessarie se non conosci questo formato:

    • XLSM vuol dire che il file ha delle macro. Le macro possono essere malevole, quindi alcune organizzazioni le bloccano di default. In File > Opzioni > Centro protezione puoi controllare le opzioni di sicurezza relative alla macro
    • Scaricare un file con macro potrebbe indicare che alcune funzionalità sono bloccate, quindi ti consiglio di selezionare il file > tasto destro > proprietà e controllare che non ci siano blocchi specifici

    L'intero codice della macro lo trovi in questa guida - quindi, se non ti fidi, puoi copiarlo e rivederlo pezzo per pezzo prima di utilizzarlo. Un controllo aggiuntivo non fa mai male.

    Un piccolo disclaimer - nella guida uso sanificare, sanitizzare, anonimizzare in maniera praticamente intercambiabile. A livello tecnico, i termini più corretti sono relativi alla generazione dei dati sintetici e ciò che descriviamo è più ascrivibile alla pseduo-anonimizzazione dei dati, ma per semplicità ho tenuto così, credo sia più logico almeno per una discussione "alta" sul tema senza dover andare a parlare di DPO, GDPR o simili. Inoltre, la macro è relativa alla versione italiana di Excel, anche se può essere adattata visto che il codice è liberamente scaricabile.

    Perché anonimizzare i dati in Excel

    Partiamo da una tabella molto semplice.

    ClienteRegioneProdottoFatturatoVariazione
    Rossi SRLLombardiaProdotto A12.500 €10%
    Bianchi SPALazioProdotto B-8.300 €-5%
    Rossi SRLLombardiaProdotto C9.400 €10%

    Se dovessi inviare questo file per chiedere aiuto su una formula o su una query, probabilmente non avrei alcun motivo per condividere il vero nome dei clienti. Potrei invece voler mantenere Regione, Prodotto e altri dati che non considero sensibili, perché possono essere utili per riprodurre correttamente il problema.

    Una prima possibilità sarebbe sostituire tutto con XXX, ma perderemmo quasi completamente la struttura del dataset. Non sapremmo più, ad esempio, che Rossi SRL compare due volte, non distingueremmo importi positivi e negativi e non potremmo più utilizzare alcuni campi originali che magari non avevano alcuna necessità di essere modificati.

    Con SANITIZE possiamo invece decidere, ad esempio, di anonimizzare soltanto Cliente e Fatturato.

    ClienteRegioneProdottoFatturatoVariazione
    CLI-A7K2-P9Q4LombardiaProdotto A18.724,31 €10%
    CLI-H5N8-X2R1LazioProdotto B-6.381,15 €-5%
    CLI-A7K2-P9Q4LombardiaProdotto C27.412,80 €10%

    I dati che abbiamo deciso di proteggere sono stati sostituiti, mentre le altre colonne sono rimaste invariate.

    Come funziona SANITIZE

    COME ANONIMIZZARE DATI MICROSOFT EXCEL MARCOFILOCAMO SCHEMA VISUALE

    Per evitare di inserire la macro in ogni workbook da elaborare, utilizziamo un file separato chiamato SANITIZE.xlsm. Questo file contiene il codice VBA e può essere riutilizzato ogni volta che dobbiamo preparare una tabella da condividere.

    Supponiamo di avere un file chiamato Vendite 2026.xlsx con una Tabella Excel chiamata TBL_VENDITE. Avviando SANITIZE possiamo creare automaticamente Vendite 2026_SANITIZED.xlsx, contenente un foglio e una Tabella Excel chiamati TBL_VENDITE_SANITIZED.

    Il flusso è semplice: apriamo SANITIZE, selezioniamo il file sorgente e scegliamo la tabella da elaborare. A quel punto possiamo decidere se anonimizzare l'intera tabella oppure soltanto alcune colonne. Infine scegliamo dove salvare il risultato e viene creato un nuovo file .xlsx.

    Il vantaggio di questo approccio è che non stiamo modificando il workbook originale e non stiamo nemmeno creando una sua copia completa. SANITIZE costruisce un nuovo file contenente soltanto la tabella selezionata.

    Come utilizzare SANITIZE per anonimizzare i dati

    Per utilizzare la macro è necessario che i dati siano contenuti in una Tabella Excel. Se stai lavorando con un normale intervallo puoi trasformarlo rapidamente in tabella selezionando i dati e utilizzando CTRL+T.

    1. Apri SANITIZE.xlsm.
    2. Abilita le macro.
    3. Avvia SanificaFileExcel.
    4. Seleziona il file da elaborare.
    5. Se il file contiene più Tabelle Excel, seleziona una cella della tabella interessata.
    6. Scegli se anonimizzare tutte le colonne oppure soltanto alcune.
    7. Se necessario, seleziona nel foglio le colonne da anonimizzare.
    8. Scegli il nome e la posizione del nuovo file.
    9. Controlla il risultato prima di condividerlo.

    Se il file contiene una sola Tabella Excel, questa viene utilizzata automaticamente. Prima dell'elaborazione compare inoltre la finestra Salva con nome: se il file originale si chiama Vendite 2026.xlsm, il nome proposto sarà automaticamente Vendite 2026_SANITIZED.xlsx.

    Se annulli questa finestra, la procedura termina senza creare alcun nuovo workbook. Puoi scaricare il file utilizzato in questa guida e riutilizzarlo per anonimizzare le tue Tabelle Excel.

    Ti segnalo inoltre un disclaimer per chiarire meglio che cosa fa SANITIZE - ogni utilizzo richiede un controllo umano successivo.

    Dopo aver scaricato il file, aprilo con Excel desktop e abilita le macro. Excel Online non supporta l'esecuzione di codice VBA.

    CONSULENZA EXCEL

    Vuoi applicare quanto visto ai tuoi dati e processi aziendali?

    Se il tuo caso richiede più di un esempio standard, posso aiutarti a progettare o ottimizzare il file e automatizzare le attività ripetitive.

    Marco Filocamo

    Marco Filocamo · Microsoft Excel MVP

    Cerchi invece formazione Excel per il tuo team? Scopri la formazione aziendale

    Come anonimizzare i dati Excel con un esempio pratico

    Prendiamo i salari della F1 per la stagione 2026-2027. Le scuderie di F1 hanno sempre due piloti in griglia e il dataset creato è molto semplice: avremo nome, cognome, scuderia e ricavi potenziali. Niente di complesso, ma utile per il nostro scopo.

    COME ANONIMIZZARE DATI MICROSOFT EXCEL MARCOFILOCAMO FILE INIZIALE

    Perchè può essere utile, un esempio simile? Perchè abbiamo, ad esempio:

    • Max | Verstappen | Red Bull | 81
    • Lewis | Hamilton | Ferrari | 70,5
    • Lando | Norris | McLaren | 69,5
    • Charles | Leclerc | Ferrari | 50

    Se dovessi fare analisi su questi dati, vorrei solo sapere quali sono le intestazioni delle colonne e i tipi di dati inseriti. Non è per forza necessario sapere che Leclerc abbia dei guadagni potenziali di 20,5 milioni superiori rispetto ad Hamilton - ma io potrei impostare tutto il mio file finale di analisi, usando query e formule, usando dati fittizi da riportare poi in un sistema reale.

    Se vuoi seguire passo passo la guida, scarica il file dei piloti qui - altrimenti, prova su un tuo file!

    Assumiamo di voler scoprire la media di salario per ciascun team.

    COME ANONIMIZZARE DATI MICROSOFT EXCEL MARCOFILOCAMO START PILOTI

    In questo caso creo una formula del tipo:

    =RAGGRUPPAPER(TBL_F1_DRIVERS[TEAM];TBL_F1_DRIVERS[POTENTIAL_EARNINGS];MEDIA;0;0;-2)

    In pratica sto raggruppando i singoli team, facendo la media per guadagni (potential_earnings) e ordinandoli dal team con la media più alta a quello con la media più bassa.

    Nel dataset, però, sappiamo chi prende di più e chi di meno - possiamo vedere molto chiaramente che Hamilton prende più di Leclerc. Se avessimo dei dati fittizi, potremmo fare lo stesso calcolo con la stessa formula - certo, non sarebbe interpretabile, ma la logica sarebbe perfettamente sovrapponibile al file iniziale con i salari reali. Anche se avessimo Hamilton con 200 milioni e Leclerc con 700 milioni, la formula non cambierebbe - cambierebbe il totale, ma potremmo mantenere la stessa struttura dei dati, inserendo poi ex post quelli reali.

    Il flusso, in azienda, potrebbe essere:

    • Devo fare eseguire dei calcoli su un dataset sensibile. Mettiamo di avere i salari aziendali e di dover fare analisi sulla media per reparto
    • Voglio anonimizzare tutti i dati o parte di essi. Qui, per esempio, potremmo voler nascondere i nomi dei piloti, ma tenere i nomi delle scuderie e relativi salari. In un caso aziendale, anonimizzare qualunque informazione dei dipendenti è sempre la strada migliore
    • Ottenuto il file finale, posso eseguire una serie di calcoli con le formule sui dati fittizi. Le stesse formule, potenzialmente, potrei usarle anche sul file iniziale - ma se mi interessano solo aggregazioni, non ho problemi a condividere i file anche con terzi, se ho passato il test di anonimizzazione

    In sintesi - la formula non cambia, cambiano i dati inseriti. Se i dati sono sensibili, in particolare, questo è un tema cruciale.

    Per chiarire che i dati non sono quelli originali, il nome della tabella e del file avranno il suffisso _sanitized. Vediamo nel pratico come funziona.

    COME ANONIMIZZARE DATI MICROSOFT EXCEL MARCOFILOCAMO PARTENZA

    Aprendo SANITIZE, puoi cliccare sul grande pulsante che trovi e avviare la macro.

    COME ANONIMIZZARE DATI MICROSOFT EXCEL MARCOFILOCAMO SELEZIONE FILE

    Prendiamo il file precedente, che si chiama F1_DRIVERS. Chiaramente puoi provare con il file che preferisci, facendo sempre attenzione a fare un controllo finale prima di condividere il file con dati sintetici.

    COME ANONIMIZZARE DATI MICROSOFT EXCEL MARCOFILOCAMO SCELTA COLONNE TUTTE

    Puoi scegliere tre opzioni:

    • Yes = sì, anonimizzo tutte le colonne
    • No = no, solo alcune
    • Cancel = annulla l'operazione

    Puoi modificare la macro per mettere tutto in italiano, se preferisci!

    COME ANONIMIZZARE DATI MICROSOFT EXCEL MARCOFILOCAMO SCELTA NOME FILE POSIZIONE

    Di default, la macro creerà un nuovo file chiamato fileiniziale_SANITIZED, per chiarire che il file ha attraversato un processo di modifica.

    COME ANONIMIZZARE DATI MICROSOFT EXCEL MARCOFILOCAMO RECAP OPERAZIONI

    Al termine dell'operazione, la macro fa un recap delle operazioni effettuate - in questo caso, ventidue righe per quattro colonne (88 celle) sono state modificate.

    COME ANONIMIZZARE DATI MICROSOFT EXCEL MARCOFILOCAMO ESEMPIO FINALE

    Come noti, è ora impossibile capire quali siano i piloti più pagati o a quali scuderie appartengano, dato che tutte le informazioni sono state sostituite con dati sintetici.

    C'è un dettaglio importante, però. I dati originali sono sì sostituiti, ma coerentemente - se un testo viene modificato, il risultato sarà uguale. Questo elemento toglie una parte di vera anonimizzazione, dato che, se avessimo 50 transazioni di un cliente e 2 di un altro, sarebbe facilissimo capire chi è chi.

    In un ambito aziendale, trovo corretto questo ragionamento perchè altrimenti sarebbe impossibile fare aggregazioni o calcoli sui dati fittizi - se ogni cella è completamente indipendente, non è possibile fare operazioni. Tienilo però a mente per i tuoi casi e, se vuoi, puoi sempre modificare la macro come preferisci.

    COME ANONIMIZZARE DATI MICROSOFT EXCEL MARCOFILOCAMO DETTAGLIO FERRARI

    Qui vediamo, ad esempio che un team è stato cambiato con TEA-8QP2-PZL6. Qual è il team? Non lo sappiamo ma, come noti, il valore è restituito due volte. Ciò è coerente con il nostro utilizzo, visto che potremo fare calcoli, anche se su dati fittizi.

    COME ANONIMIZZARE DATI MICROSOFT EXCEL MARCOFILOCAMO DETTAGLIO NOMI MANTENUTI

    Nello specifico, TEA-8QP2-PZL6 è il valore di Ferrari. Questa informazione puoi ricavarla, però, solo andando a controllare il file precedente, altrimenti non sarebbe possibile scoprirla in autonomia, dato che tutti i team hanno effettivamente due piloti a roster.

    Come anonimizzare solo parte di un file Excel

    Vediamo un caso diverso, nel quale vogliamo usare lo stesso dataset ma anonimizzare solo alcune colonne. Vogliamo mantenere invariati i valori di team e guadagni, nascondendo però nomi e cognomi dei piloti. Vediamo come fare.

    COME ANONIMIZZARE DATI MICROSOFT EXCEL MARCOFILOCAMO SOLO ALCUNE COLONNE

    Il flusso è identico al precedente. Aprendo SANITIZE, arriverai poi alla scelta del numero di colonne da modificare e in questo caso dovrai dire No.

    COME ANONIMIZZARE DATI MICROSOFT EXCEL MARCOFILOCAMO SELEZIONE COLONNE

    A questo punto apriamo il file precedente, selezionando solo NAME e SURNAME, cioè nome e cognome. La nostra aspettativa è che questi valori diventino impossibile da comprendere, con il nome di scuderia (TEAM) e guadagni (POTENTIAL_EARNINGS) invariati.

    COME ANONIMIZZARE DATI MICROSOFT EXCEL MARCOFILOCAMO REVISIONE FLUSSO

    La macro prende quindi i nostri dati, indicando chiaramente quante siano le colonne da modificare e da tenere invariate.

    COME ANONIMIZZARE DATI MICROSOFT EXCEL MARCOFILOCAMO SALVATAGGIO NUOVO FILE

    Salviamo il file, come nel caso precedente - ma stavolta, ci aspettiamo un altro output, perchè dobbiamo poter fare calcoli su scuderia e guadagni.

    COME ANONIMIZZARE DATI MICROSOFT EXCEL MARCOFILOCAMO RISULTATO FINALE

    Vediamo come la macro abbia fatto ciò che volevamo! Abbiamo infatti una lista di piloti, ma non possiamo determinare chi sia il più pagato nelle singole scuderie.

    COME ANONIMIZZARE DATI MICROSOFT EXCEL MARCOFILOCAMO SOLO ALCUNE COLONNE PILOTI

    Possiamo quindi eseguire lo stesso identico calcolo precedente, cambiando solo il nome della tabella:

    =RAGGRUPPAPER(TBL_F1_DRIVERS_SANITIZED[TEAM];TBL_F1_DRIVERS_SANITIZED[POTENTIAL_EARNINGS];MEDIA;0;0;-2)

    Sappiamo che, in ordine, Ferrari, McLaren e Red Bull sono quelle con i piloti con maggior potenziale di guadagno, in media. Impossibile sarebbe però sapere chi sia Leclerc e Hamilton, in questo caso.

    In azienda, questo rende possibile anonimizzare colonne selezionate di un file in modo semplice. Come sempre, però, è importante fare un controllo finale per evitare che i valori possano comunque essere riconoscibili.

    Come scegliere le colonne Excel da anonimizzare (dettaglio)

    Dopo aver individuato la tabella, come abbiamo visto, SANITIZE chiede se vogliamo anonimizzare tutte le colonne.

    Se scegliamo , tutti i valori della tabella vengono sostituiti con dati sintetici. Se scegliamo No, possiamo selezionare direttamente nel foglio le colonne che vogliamo anonimizzare.

    Non è necessario selezionare tutte le celle della colonna: possiamo utilizzare anche soltanto le intestazioni oppure una qualsiasi cella appartenente alle colonne interessate. Per selezionare colonne non adiacenti possiamo utilizzare CTRL su Windows oppure CMD su Mac.

    Ad esempio, in una tabella composta da:

    Cliente | Regione | Prodotto | Quantità | Fatturato

    potremmo scegliere di anonimizzare soltanto:

    Cliente | Fatturato

    SANITIZE modificherà quindi queste due colonne e copierà senza modifiche Regione, Prodotto e Quantità.

    Prima di procedere viene mostrato un avviso specifico, perché in questa modalità il nuovo file continuerà a contenere i valori originali delle colonne che non abbiamo selezionato. La scelta delle colonne deve quindi essere fatta con attenzione.

    Come vengono generati i dati sintetici

    Per ogni colonna selezionata la macro cerca di capire quale tipo di informazione è presente. In alcuni casi utilizza il nome dell'intestazione, mentre per i valori numerici controlla anche il formato Excel applicato alle celle.

    Una colonna Email, ad esempio, può produrre un valore come:

    [email protected]

    Una colonna Telefono può diventare:

    TEL-5831940276

    Per una normale colonna testuale come Cliente viene invece generato un codice sintetico che utilizza una parte dell'intestazione come prefisso:

    CLI-A7K2-P9Q4

    La macro gestisce diversi casi comuni, fra cui email, URL, IBAN, telefoni, CAP, codici, date, orari, durate, età, percentuali, importi, valute e numeri generici. Se non viene riconosciuto un tipo particolare, viene comunque generato un valore coerente con la natura del dato originale.

    Le colonne che non abbiamo selezionato non passano invece attraverso questa logica: il loro contenuto viene riportato nel nuovo file senza modifiche.

    Come mantenere i valori ripetuti

    Uno degli aspetti più importanti riguarda i duplicati. Se lo stesso cliente compare più volte all'interno della stessa colonna, non vogliamo generare ogni volta un valore differente, perché perderemmo un'informazione utile sulla struttura della tabella.

    La prima volta che SANITIZE incontra Rossi SRL, ad esempio, potrebbe associarlo a CLI-A7K2-P9Q4. Se lo stesso valore compare nuovamente nella colonna Cliente, verrà utilizzato ancora lo stesso valore sintetico.

    La corrispondenza tra dato originale e dato sintetico viene mantenuta solamente in memoria durante l'esecuzione. Non viene scritta nel file finale e non viene salvata altrove, quindi una nuova esecuzione della macro produrrà una mappatura differente.

    Questo permette di mantenere utilizzabili conteggi, raggruppamenti e altre analisi che dipendono dalla presenza di valori ripetuti.

    Come mantenere numeri positivi e negativi

    Per i valori numerici anonimizzati viene mantenuto anche il segno originale. Nei dati aziendali un numero negativo può indicare uno storno, un reso, una rettifica o un movimento in uscita, quindi trasformarlo casualmente in un valore positivo altererebbe il significato della tabella.

    La regola utilizzata è semplice:

    positivo → positivo
    negativo → negativo
    zero → zero

    Un valore di 1.500 € può diventare, ad esempio, 64.239,85 €, mentre -1.500 € può diventare -28.431,17 €. Lo stesso comportamento viene applicato alle percentuali e agli altri valori numerici per i quali il segno ha significato.

    La macro genera direttamente il nuovo valore nel verso corretto e applica anche un secondo controllo prima di inserirlo nella tabella.

    Come gestire date, percentuali e valute

    In Excel una data, una percentuale e un orario sono tecnicamente numeri. Non possiamo quindi riconoscerli semplicemente controllando il contenuto della cella.

    La macro analizza anche il formato numerico applicato da Excel. Un valore visualizzato come 15/05/2026 può quindi essere riconosciuto come data, 12,5% come percentuale e 1.250,00 € come importo in valuta.

    Dopo aver generato il nuovo valore, SANITIZE applica nuovamente il formato originale. In questo modo una percentuale rimane visualizzata come percentuale, una data continua ad apparire come data e un importo mantiene il formato valuta.

    La stessa logica viene utilizzata anche per data e ora, orari e durate.

    Come viene protetto il file originale

    Il workbook sorgente viene aperto in sola lettura utilizzando il parametro ReadOnly:

    ReadOnly:=True

    Alla fine viene chiuso senza salvare modifiche:

    wbOrigine.Close SaveChanges:=False

    In questo modo SANITIZE non deve cancellare, sostituire o modificare i dati presenti nel file originale.

    Prima dell'apertura viene inoltre utilizzata questa istruzione:

    Application.AutomationSecurity = _
        msoAutomationSecurityForceDisable

    per evitare l'esecuzione automatica di eventuali macro VBA presenti nel workbook selezionato. Una volta aperto il file viene ripristinata l'impostazione precedente.

    Come vengono generati i valori casuali

    Questo punto è fondamentale. Per generare i nuovi valori non utilizzo il normale Rnd di VBA, ma la macro si appoggia invece al generatore casuale fornito dal sistema operativo.

    Su Windows viene utilizzato BCryptGenRandom, mentre su macOS viene utilizzato arc4random_buf. Ogni nuova esecuzione produce quindi una mappatura differente: lo stesso cliente che in una prima elaborazione diventa CLI-A7K2-P9Q4 potrebbe ottenere un codice completamente diverso la volta successiva.

    In sostanza - è praticamente impossibile, per un utente che non abbia visione dei dati precedenti, fare reverse engineering e riuscire a ricavare i dati prima del passaggio nella macro.

    Codice VBA per anonimizzare i dati in Excel

    Il codice completo gestisce la selezione del file, della tabella e delle colonne da anonimizzare, oltre alla creazione del nuovo workbook con i dati sintetici. La macro è molto ricca e articolata, ti lascio qui il codice qualora volessi darci un'occhiata ma eviterò di andare troppo in dettaglio nella guida.

    Option Explicit
    
    '=========================================================
    ' SANITIZE.xlsm
    '
    ' Compatibile con:
    ' - Excel per Windows
    ' - Excel per macOS
    '
    ' FUNZIONALITÀ
    ' - Selezione file sorgente
    ' - Apertura in sola lettura
    ' - Selezione Tabella Excel
    ' - Possibilità di anonimizzare:
    '       1. tutte le colonne
    '       2. solo alcune colonne selezionate
    ' - Dati sintetici casuali
    ' - Valori uguali -> stesso valore sintetico
    ' - Segno numerico preservato
    ' - Formati Excel mantenuti
    ' - Nuovo file .xlsx
    '
    ' ATTENZIONE
    ' Se vengono selezionate solo alcune colonne,
    ' le colonne non selezionate vengono copiate
    ' nel file finale SENZA anonimizzazione.
    '=========================================================
    
    
    '=========================================================
    ' RANDOM SICURO CROSS-PLATFORM
    '=========================================================
    
    #If Mac Then
    
        Private Declare PtrSafe Sub arc4random_buf _
            Lib "/usr/lib/libSystem.B.dylib" ( _
            ByRef buffer As Any, _
            ByVal dimensione As LongPtr)
    
    #Else
    
        Private Declare PtrSafe Function BCryptGenRandom _
            Lib "bcrypt.dll" ( _
            ByVal hAlgorithm As LongPtr, _
            ByRef pbBuffer As Any, _
            ByVal cbBuffer As Long, _
            ByVal dwFlags As Long) As Long
    
        Private Const BCRYPT_USE_SYSTEM_PREFERRED_RNG As Long = &H2
    
    #End If
    
    
    '=========================================================
    ' PROCEDURA PRINCIPALE
    '=========================================================
    
    Public Sub SanificaFileExcel()
    
        Const SOGLIA_AVVISO_CELLE As Double = 2000000#
    
        Dim percorsoOrigine As Variant
        Dim percorsoOutput As Variant
        Dim percorsoOutputFinale As String
    
        Dim nomeFileOrigine As String
        Dim nomeFileDefault As String
        Dim nomeOutput As String
    
        Dim wbOrigine As Workbook
        Dim wbDestinazione As Workbook
        Dim wsDestinazione As Worksheet
    
        Dim tblOrigine As ListObject
        Dim tblDestinazione As ListObject
    
        Dim rngOutput As Range
    
        Dim dati As Variant
        Dim intestazioni As Variant
        Dim output() As Variant
    
        Dim mappe() As Collection
        Dim valoriUsati() As Collection
    
        Dim sanitizzaColonna() As Boolean
    
        Dim formatiColonna() As String
        Dim formatiMisti() As Boolean
    
        Dim formatoColonna As Variant
        Dim formatoCorrente As String
    
        Dim righe As Long
        Dim colonne As Long
    
        Dim r As Long
        Dim c As Long
    
        Dim chiave As String
        Dim chiaveSintetica As String
    
        Dim valoreSintetico As Variant
    
        Dim numeroTabelle As Long
        Dim numeroCelle As Double
    
        Dim numeroColonneSanitizzate As Long
    
        Dim tentativi As Long
        Dim passoAvanzamento As Long
    
        Dim stileTabella As String
        Dim risposta As VbMsgBoxResult
    
        Dim statoScreenUpdating As Boolean
        Dim statoEnableEvents As Boolean
        Dim statoDisplayAlerts As Boolean
    
        Dim sicurezzaPrecedente As MsoAutomationSecurity
    
        Dim descrizioneErrore As String
    
        Dim sovrascriviOutput As Boolean
        Dim outputCompleto As Boolean
    
    
        '-----------------------------------------------------
        ' Stato iniziale Excel
        '-----------------------------------------------------
    
        statoScreenUpdating = _
            Application.screenUpdating
    
        statoEnableEvents = _
            Application.enableEvents
    
        statoDisplayAlerts = _
            Application.displayAlerts
    
        sicurezzaPrecedente = _
            Application.automationSecurity
    
    
        On Error GoTo GestioneErrore
    
    
        '=====================================================
        ' 1. SELEZIONE FILE SORGENTE
        '=====================================================
    
        percorsoOrigine = _
            Application.GetOpenFilename( _
                Title:="Seleziona il file Excel da sanitizzare")
    
    
        If VarType(percorsoOrigine) = vbBoolean Then
    
            If percorsoOrigine = False Then
                Exit Sub
            End If
    
        End If
    
    
        If Not EstensioneExcelSupportata( _
            CStr(percorsoOrigine)) Then
    
            MsgBox _
                "Seleziona un file Excel nei formati:" & _
                vbCrLf & vbCrLf & _
                ".xlsx" & vbCrLf & _
                ".xlsm" & vbCrLf & _
                ".xlsb" & vbCrLf & _
                ".xls", _
                vbExclamation, _
                "Formato non supportato"
    
            Exit Sub
    
        End If
    
    
        '-----------------------------------------------------
        ' SANITIZE.xlsm non può essere il sorgente
        '-----------------------------------------------------
    
        If StrComp( _
            CStr(percorsoOrigine), _
            ThisWorkbook.FullName, _
            vbTextCompare) = 0 Then
    
            MsgBox _
                "Seleziona il file contenente i dati da sanitizzare, " & _
                "non SANITIZE.xlsm.", _
                vbExclamation, _
                "File non valido"
    
            Exit Sub
    
        End If
    
    
        '-----------------------------------------------------
        ' Il sorgente deve essere chiuso
        '-----------------------------------------------------
    
        If WorkbookGiaAperto( _
            CStr(percorsoOrigine)) Then
    
            MsgBox _
                "Il file selezionato è già aperto." & _
                vbCrLf & vbCrLf & _
                "Chiudilo e avvia nuovamente SANITIZE.", _
                vbExclamation, _
                "File già aperto"
    
            Exit Sub
    
        End If
    
    
        Application.screenUpdating = False
        Application.enableEvents = False
    
        Application.StatusBar = _
            "SANITIZE - Apertura del file sorgente..."
    
    
        '=====================================================
        ' 2. APERTURA READ-ONLY
        '=====================================================
    
        Application.automationSecurity = _
            msoAutomationSecurityForceDisable
    
    
        Set wbOrigine = _
            Workbooks.Open( _
                Filename:=CStr(percorsoOrigine), _
                UpdateLinks:=0, _
                ReadOnly:=True, _
                AddToMru:=False, _
                Notify:=False, _
                IgnoreReadOnlyRecommended:=True)
    
    
        Application.automationSecurity = _
            sicurezzaPrecedente
    
    
        nomeFileOrigine = _
            wbOrigine.Name
    
    
        '=====================================================
        ' 3. INDIVIDUAZIONE TABELLA
        '=====================================================
    
        numeroTabelle = _
            ContaTabelle( _
                wbOrigine, _
                tblOrigine)
    
    
        If numeroTabelle = 0 Then
    
            MsgBox _
                "Il file selezionato non contiene Tabelle Excel." & _
                vbCrLf & vbCrLf & _
                "Trasforma prima l'intervallo da sanitizzare " & _
                "in una Tabella Excel.", _
                vbExclamation, _
                "Nessuna tabella trovata"
    
            GoTo AnnullaOperazione
    
        End If
    
    
        If numeroTabelle > 1 Then
    
            Application.screenUpdating = True
    
    
            Set tblOrigine = _
                SelezionaTabellaDalWorkbook( _
                    wbOrigine)
    
    
            Application.screenUpdating = False
    
    
            If tblOrigine Is Nothing Then
                GoTo AnnullaOperazione
            End If
    
        End If
    
    
        If tblOrigine.DataBodyRange Is Nothing Then
    
            MsgBox _
                "La tabella selezionata non contiene dati.", _
                vbExclamation, _
                "Tabella vuota"
    
            GoTo AnnullaOperazione
    
        End If
    
    
        '=====================================================
        ' 4. DIMENSIONI
        '=====================================================
    
        righe = _
            tblOrigine.ListRows.Count
    
        colonne = _
            tblOrigine.ListColumns.Count
    
    
        numeroCelle = _
            CDbl(righe) * _
            CDbl(colonne)
    
    
        ReDim sanitizzaColonna( _
            1 To colonne)
    
    
        '=====================================================
        ' 5. SCELTA DELLE COLONNE DA ANONIMIZZARE
        '=====================================================
    
        risposta = _
            MsgBox( _
                "Vuoi anonimizzare tutte le colonne della tabella?" & _
                vbCrLf & vbCrLf & _
                "Sì = anonimizza tutte le colonne" & _
                vbCrLf & _
                "No = scegli solo alcune colonne" & _
                vbCrLf & _
                "Annulla = interrompi", _
                vbYesNoCancel + vbQuestion, _
                "Colonne da anonimizzare")
    
    
        If risposta = vbCancel Then
            GoTo AnnullaOperazione
        End If
    
    
        If risposta = vbYes Then
    
            For c = 1 To colonne
    
                sanitizzaColonna(c) = True
    
            Next c
    
            numeroColonneSanitizzate = _
                colonne
    
    
        Else
    
    
            Application.screenUpdating = True
    
    
            If Not SelezionaColonneDaSanitizzare( _
                tblOrigine, _
                sanitizzaColonna, _
                numeroColonneSanitizzate) Then
    
    
                Application.screenUpdating = False
    
                GoTo AnnullaOperazione
    
    
            End If
    
    
            Application.screenUpdating = False
    
    
            '-------------------------------------------------
            ' Avviso importante:
            ' le altre colonne rimarranno originali
            '-------------------------------------------------
    
            risposta = _
                MsgBox( _
                    "Hai scelto di anonimizzare " & _
                    numeroColonneSanitizzate & _
                    " colonne su " & _
                    colonne & _
                    "." & _
                    vbCrLf & vbCrLf & _
                    "Le altre " & _
                    (colonne - numeroColonneSanitizzate) & _
                    " colonne verranno copiate nel nuovo file " & _
                    "con i valori ORIGINALI." & _
                    vbCrLf & vbCrLf & _
                    "Continua solo se queste colonne possono essere condivise." & _
                    vbCrLf & vbCrLf & _
                    "Continuare?", _
                    vbYesNo + vbExclamation + vbDefaultButton2, _
                    "Dati non anonimizzati")
    
    
            If risposta <> vbYes Then
                GoTo AnnullaOperazione
            End If
    
    
        End If
    
    
        '=====================================================
        ' 6. AVVISO DATASET GRANDE
        '=====================================================
    
        If numeroCelle > SOGLIA_AVVISO_CELLE Then
    
            risposta = _
                MsgBox( _
                    "La tabella contiene:" & _
                    vbCrLf & vbCrLf & _
                    Format$(righe, "#,##0") & " righe" & _
                    vbCrLf & _
                    Format$(colonne, "#,##0") & " colonne" & _
                    vbCrLf & _
                    Format$(numeroCelle, "#,##0") & " celle" & _
                    vbCrLf & vbCrLf & _
                    "La sanitizzazione potrebbe richiedere " & _
                    "più memoria e tempo del normale." & _
                    vbCrLf & vbCrLf & _
                    "Continuare?", _
                    vbYesNo + vbQuestion + vbDefaultButton2, _
                    "Tabella di grandi dimensioni")
    
    
            If risposta <> vbYes Then
                GoTo AnnullaOperazione
            End If
    
        End If
    
    
        '=====================================================
        ' 7. NOMI OUTPUT
        '=====================================================
    
        nomeOutput = _
            CreaNomeOutput( _
                tblOrigine.Name)
    
    
        nomeFileDefault = _
            NomeFileSanitizzato( _
                nomeFileOrigine)
    
    
        stileTabella = _
            tblOrigine.TableStyle
    
    
        '=====================================================
        ' 8. SALVA CON NOME PRIMA DELL'ELABORAZIONE
        '=====================================================
    
    RichiediPercorsoOutput:
    
    
        sovrascriviOutput = False
    
        Application.StatusBar = False
        Application.screenUpdating = True
    
    
    #If Mac Then
    
        percorsoOutput = _
            Application.GetSaveAsFilename( _
                InitialFileName:=nomeFileDefault, _
                Title:="Salva il file sanitizzato")
    
    #Else
    
        percorsoOutput = _
            Application.GetSaveAsFilename( _
                InitialFileName:=nomeFileDefault, _
                FileFilter:= _
                    "Cartella di lavoro Excel (*.xlsx),*.xlsx", _
                FilterIndex:=1, _
                Title:="Salva il file sanitizzato")
    
    #End If
    
    
        Application.screenUpdating = False
    
    
        If VarType(percorsoOutput) = vbBoolean Then
    
            If percorsoOutput = False Then
                GoTo AnnullaOperazione
            End If
    
        End If
    
    
        percorsoOutputFinale = _
            AssicuraEstensioneXlsx( _
                CStr(percorsoOutput))
    
    
        '-----------------------------------------------------
        ' Non può sovrascrivere il sorgente
        '-----------------------------------------------------
    
        If StrComp( _
            percorsoOutputFinale, _
            CStr(percorsoOrigine), _
            vbTextCompare) = 0 Then
    
            MsgBox _
                "Il file sanitizzato non può sovrascrivere " & _
                "il file originale.", _
                vbExclamation, _
                "Percorso non valido"
    
            GoTo RichiediPercorsoOutput
    
        End If
    
    
        '-----------------------------------------------------
        ' File già esistente
        '-----------------------------------------------------
    
        If FileEsiste( _
            percorsoOutputFinale) Then
    
            risposta = _
                MsgBox( _
                    "Esiste già un file con questo nome:" & _
                    vbCrLf & vbCrLf & _
                    percorsoOutputFinale & _
                    vbCrLf & vbCrLf & _
                    "Vuoi sostituirlo?", _
                    vbYesNo + vbExclamation + vbDefaultButton2, _
                    "File già esistente")
    
    
            If risposta <> vbYes Then
                GoTo RichiediPercorsoOutput
            End If
    
    
            sovrascriviOutput = True
    
        End If
    
    
        '=====================================================
        ' 9. LETTURA DATI
        '=====================================================
    
        Application.StatusBar = _
            "SANITIZE - Lettura dei dati..."
    
    
        dati = _
            ValoriRange2D( _
                tblOrigine.DataBodyRange)
    
    
        intestazioni = _
            ValoriRange2D( _
                tblOrigine.HeaderRowRange)
    
    
        ReDim output( _
            1 To righe + 1, _
            1 To colonne)
    
    
        ReDim mappe( _
            1 To colonne)
    
    
        ReDim valoriUsati( _
            1 To colonne)
    
    
        ReDim formatiColonna( _
            1 To colonne)
    
    
        ReDim formatiMisti( _
            1 To colonne)
    
    
        '=====================================================
        ' 10. PREPARAZIONE COLONNE
        '=====================================================
    
        For c = 1 To colonne
    
    
            output(1, c) = _
                intestazioni(1, c)
    
    
            '-------------------------------------------------
            ' Le Collection servono solamente
            ' alle colonne da anonimizzare
            '-------------------------------------------------
    
            If sanitizzaColonna(c) Then
    
                Set mappe(c) = _
                    New Collection
    
                Set valoriUsati(c) = _
                    New Collection
    
            End If
    
    
            formatoColonna = _
                tblOrigine.ListColumns(c) _
                    .DataBodyRange _
                    .NumberFormatLocal
    
    
            If IsNull(formatoColonna) Then
    
    
                formatiMisti(c) = True
    
    
                formatiColonna(c) = _
                    CStr( _
                        tblOrigine.DataBodyRange _
                            .Cells(1, c) _
                            .NumberFormatLocal)
    
    
            Else
    
    
                formatiMisti(c) = False
    
    
                formatiColonna(c) = _
                    CStr(formatoColonna)
    
    
            End If
    
    
        Next c
    
    
        passoAvanzamento = _
            righe \ 100
    
    
        If passoAvanzamento < 1 Then
            passoAvanzamento = 1
        End If
    
    
        '=====================================================
        ' 11. ELABORAZIONE
        '=====================================================
    
        For r = 1 To righe
    
    
            If _
                r = 1 Or _
                r = righe Or _
                r Mod passoAvanzamento = 0 Then
    
    
                Application.StatusBar = _
                    "SANITIZE - Elaborazione: " & _
                    Format$(r / righe, "0%") & _
                    " (" & _
                    Format$(r, "#,##0") & _
                    " / " & _
                    Format$(righe, "#,##0") & _
                    " righe)"
    
    
                DoEvents
    
    
            End If
    
    
            For c = 1 To colonne
    
    
                '=================================================
                ' COLONNA NON SELEZIONATA
                '
                ' Il valore viene copiato senza modifiche.
                '=================================================
    
                If Not sanitizzaColonna(c) Then
    
    
                    output(r + 1, c) = _
                        dati(r, c)
    
    
                '=================================================
                ' COLONNA DA ANONIMIZZARE
                '=================================================
    
                Else
    
    
                    '---------------------------------------------
                    ' Vuoto
                    '---------------------------------------------
    
                    If ValoreVuoto( _
                        dati(r, c)) Then
    
    
                        output(r + 1, c) = _
                            vbNullString
    
    
                    '---------------------------------------------
                    ' Errore Excel
                    '---------------------------------------------
    
                    ElseIf IsError( _
                        dati(r, c)) Then
    
    
                        output(r + 1, c) = _
                            CVErr(xlErrNA)
    
    
                    Else
    
    
                        '-----------------------------------------
                        ' Formato
                        '-----------------------------------------
    
                        If formatiMisti(c) Then
    
    
                            formatoCorrente = _
                                CStr( _
                                    tblOrigine.DataBodyRange _
                                        .Cells(r, c) _
                                        .NumberFormatLocal)
    
    
                        Else
    
    
                            formatoCorrente = _
                                formatiColonna(c)
    
    
                        End If
    
    
                        '-----------------------------------------
                        ' Chiave originale
                        '-----------------------------------------
    
                        chiave = _
                            ChiaveValore( _
                                dati(r, c))
    
    
                        '-----------------------------------------
                        ' Nuovo mapping
                        '-----------------------------------------
    
                        If Not EsisteChiave( _
                            mappe(c), _
                            chiave) Then
    
    
                            tentativi = 0
    
    
                            Do
    
    
                                valoreSintetico = _
                                    ValoreSanitizzato( _
                                        valoreOriginale:=dati(r, c), _
                                        intestazione:=CStr( _
                                            intestazioni(1, c)), _
                                        formatoNumero:=formatoCorrente)
    
    
                                '---------------------------------
                                ' Verifica finale del segno
                                '---------------------------------
    
                                valoreSintetico = _
                                    ForzaSegnoCorretto( _
                                        valoreOriginale:=dati(r, c), _
                                        valoreSintetico:=valoreSintetico, _
                                        intestazione:=CStr( _
                                            intestazioni(1, c)), _
                                        formatoNumero:=formatoCorrente)
    
    
                                chiaveSintetica = _
                                    ChiaveValore( _
                                        valoreSintetico)
    
    
                                tentativi = _
                                    tentativi + 1
    
    
                                If tentativi > 10000 Then
    
    
                                    Err.Raise _
                                        vbObjectError + 2000, _
                                        "SanitizzaFileExcel", _
                                        "Impossibile generare un valore " & _
                                        "sintetico univoco per la colonna '" & _
                                        CStr(intestazioni(1, c)) & "'."
    
    
                                End If
    
    
                            Loop While _
                                EsisteChiave( _
                                    valoriUsati(c), _
                                    chiaveSintetica)
    
    
                            mappe(c).Add _
                                Item:=valoreSintetico, _
                                Key:=chiave
    
    
                            valoriUsati(c).Add _
                                Item:=True, _
                                Key:=chiaveSintetica
    
    
                        End If
    
    
                        output(r + 1, c) = _
                            mappe(c).Item(chiave)
    
    
                    End If
    
    
                End If
    
    
            Next c
    
    
        Next r
    
    
        '=====================================================
        ' 12. CREA NUOVO WORKBOOK
        '=====================================================
    
        Application.StatusBar = _
            "SANITIZE - Creazione del nuovo file..."
    
    
        Set wbDestinazione = _
            Workbooks.Add( _
                xlWBATWorksheet)
    
    
        Set wsDestinazione = _
            wbDestinazione.Worksheets(1)
    
    
        wsDestinazione.Name = _
            nomeOutput
    
    
        '=====================================================
        ' 13. SCRIVE I DATI
        '=====================================================
    
        Set rngOutput = _
            wsDestinazione.Range("A1").Resize( _
                righe + 1, _
                colonne)
    
    
        rngOutput.Value2 = _
            output
    
    
        '=====================================================
        ' 14. RIPRISTINA FORMATI
        '=====================================================
    
        Application.StatusBar = _
            "SANITIZE - Ripristino dei formati..."
    
    
        For c = 1 To colonne
    
    
            If formatiMisti(c) Then
    
    
                For r = 1 To righe
    
    
                    wsDestinazione.Cells( _
                        r + 1, _
                        c).NumberFormatLocal = _
                            tblOrigine.DataBodyRange _
                                .Cells(r, c) _
                                .NumberFormatLocal
    
    
                Next r
    
    
            Else
    
    
                wsDestinazione.Range( _
                    wsDestinazione.Cells(2, c), _
                    wsDestinazione.Cells( _
                        righe + 1, _
                        c)).NumberFormatLocal = _
                            formatiColonna(c)
    
    
            End If
    
    
        Next c
    
    
        '=====================================================
        ' 15. CREA TABELLA EXCEL
        '=====================================================
    
        Set tblDestinazione = _
            wsDestinazione.ListObjects.Add( _
                SourceType:=xlSrcRange, _
                Source:=rngOutput, _
                XlListObjectHasHeaders:=xlYes)
    
    
        tblDestinazione.Name = _
            nomeOutput
    
    
        On Error Resume Next
    
    
        tblDestinazione.TableStyle = _
            stileTabella
    
    
        If Err.Number <> 0 Then
    
            Err.Clear
    
            tblDestinazione.TableStyle = _
                "TableStyleMedium2"
    
        End If
    
    
        On Error GoTo GestioneErrore
    
    
        rngOutput.Columns.AutoFit
    
    
        outputCompleto = True
    
    
        '=====================================================
        ' 16. CHIUDE SORGENTE
        '=====================================================
    
        wbOrigine.Close _
            SaveChanges:=False
    
    
        Set wbOrigine = Nothing
    
    
        '=====================================================
        ' 17. SALVATAGGIO
        '=====================================================
    
        Application.StatusBar = _
            "SANITIZE - Salvataggio del file..."
    
    
        If sovrascriviOutput Then
            Application.displayAlerts = False
        End If
    
    
        wbDestinazione.SaveAs _
            Filename:=percorsoOutputFinale, _
            FileFormat:=xlOpenXMLWorkbook, _
            CreateBackup:=False
    
    
        Application.displayAlerts = _
            statoDisplayAlerts
    
    
        '=====================================================
        ' 18. RIPRISTINO EXCEL
        '=====================================================
    
        RipristinaApplicazione _
            statoScreenUpdating, _
            statoEnableEvents, _
            statoDisplayAlerts, _
            sicurezzaPrecedente
    
    
        wbDestinazione.Activate
    
    
        '=====================================================
        ' 19. FINE
        '=====================================================
    
        MsgBox _
            "Sanitizzazione completata." & _
            vbCrLf & vbCrLf & _
            "File: " & _
            wbDestinazione.Name & _
            vbCrLf & _
            "Foglio: " & _
            wsDestinazione.Name & _
            vbCrLf & _
            "Tabella: " & _
            tblDestinazione.Name & _
            vbCrLf & _
            "Righe: " & _
            Format$(righe, "#,##0") & _
            vbCrLf & _
            "Colonne totali: " & _
            Format$(colonne, "#,##0") & _
            vbCrLf & _
            "Colonne anonimizzate: " & _
            Format$(numeroColonneSanitizzate, "#,##0") & _
            vbCrLf & vbCrLf & _
            "Il file originale non è stato modificato.", _
            vbInformation, _
            "SANITIZE"
    
    
        Exit Sub
    
    
    '=========================================================
    ' ANNULLAMENTO
    '=========================================================
    
    AnnullaOperazione:
    
    
        On Error Resume Next
    
    
        If Not wbOrigine Is Nothing Then
    
            wbOrigine.Close _
                SaveChanges:=False
    
        End If
    
    
        RipristinaApplicazione _
            statoScreenUpdating, _
            statoEnableEvents, _
            statoDisplayAlerts, _
            sicurezzaPrecedente
    
    
        On Error GoTo 0
    
    
        Exit Sub
    
    
    '=========================================================
    ' GESTIONE ERRORI
    '=========================================================
    
    GestioneErrore:
    
    
        descrizioneErrore = _
            Err.Description
    
    
        On Error Resume Next
    
    
        If Not wbOrigine Is Nothing Then
    
            wbOrigine.Close _
                SaveChanges:=False
    
        End If
    
    
        If Not wbDestinazione Is Nothing Then
    
    
            If outputCompleto Then
    
                wbDestinazione.Activate
    
            Else
    
                wbDestinazione.Close _
                    SaveChanges:=False
    
            End If
    
    
        End If
    
    
        RipristinaApplicazione _
            statoScreenUpdating, _
            statoEnableEvents, _
            statoDisplayAlerts, _
            sicurezzaPrecedente
    
    
        On Error GoTo 0
    
    
        MsgBox _
            "Operazione interrotta:" & _
            vbCrLf & vbCrLf & _
            descrizioneErrore, _
            vbCritical, _
            "SANITIZE"
    
    
    End Sub
    
    
    '=========================================================
    ' SELEZIONE COLONNE DA ANONIMIZZARE
    '
    ' L'utente può selezionare:
    ' - intestazioni
    ' - celle della tabella
    ' - intere colonne
    ' - più aree
    '
    ' Qualsiasi colonna della tabella intercettata
    ' dalla selezione viene marcata per la sanitizzazione.
    '=========================================================
    
    Private Function SelezionaColonneDaSanitizzare( _
        ByVal tbl As ListObject, _
        ByRef sanitizzaColonna() As Boolean, _
        ByRef numeroColonneSelezionate As Long) As Boolean
    
    
        Dim scelta As Range
        Dim intersezione As Range
    
        Dim c As Long
    
    
    Riprova:
    
    
        Set scelta = Nothing
    
    
        tbl.Parent.Activate
    
    
        On Error Resume Next
    
    
        Set scelta = _
            Application.InputBox( _
                Prompt:= _
                    "Seleziona una o più colonne della tabella " & _
                    "che vuoi anonimizzare." & _
                    vbCrLf & vbCrLf & _
                    "Puoi selezionare le celle delle intestazioni " & _
                    "oppure qualsiasi cella delle colonne interessate." & _
                    vbCrLf & vbCrLf & _
                    "Per colonne non adiacenti usa Ctrl su Windows " & _
                    "o Cmd su Mac.", _
                Title:="Seleziona le colonne da anonimizzare", _
                Type:=8)
    
    
        On Error GoTo 0
    
    
        If scelta Is Nothing Then
    
            SelezionaColonneDaSanitizzare = False
    
            Exit Function
    
        End If
    
    
        '-----------------------------------------------------
        ' La selezione deve appartenere alla tabella
        '-----------------------------------------------------
    
        Set intersezione = Nothing
    
    
        On Error Resume Next
    
    
        Set intersezione = _
            Intersect( _
                scelta, _
                tbl.Range)
    
    
        On Error GoTo 0
    
    
        If intersezione Is Nothing Then
    
    
            MsgBox _
                "La selezione non contiene colonne della Tabella Excel." & _
                vbCrLf & vbCrLf & _
                "Seleziona almeno una cella appartenente alla tabella.", _
                vbExclamation, _
                "Selezione non valida"
    
    
            GoTo Riprova
    
    
        End If
    
    
        '-----------------------------------------------------
        ' Reset
        '-----------------------------------------------------
    
        numeroColonneSelezionate = 0
    
    
        For c = LBound(sanitizzaColonna) _
            To UBound(sanitizzaColonna)
    
            sanitizzaColonna(c) = False
    
        Next c
    
    
        '-----------------------------------------------------
        ' Identifica le colonne intercettate
        '-----------------------------------------------------
    
        For c = 1 To tbl.ListColumns.Count
    
    
            If Not Intersect( _
                scelta, _
                tbl.ListColumns(c).Range) Is Nothing Then
    
    
                sanitizzaColonna(c) = True
    
    
                numeroColonneSelezionate = _
                    numeroColonneSelezionate + 1
    
    
            End If
    
    
        Next c
    
    
        If numeroColonneSelezionate = 0 Then
    
    
            MsgBox _
                "Non è stata selezionata alcuna colonna valida.", _
                vbExclamation, _
                "Selezione non valida"
    
    
            GoTo Riprova
    
    
        End If
    
    
        SelezionaColonneDaSanitizzare = _
            True
    
    
    End Function
    
    
    '=========================================================
    ' GENERAZIONE VALORE SINTETICO
    '=========================================================
    
    Private Function ValoreSanitizzato( _
        ByVal valoreOriginale As Variant, _
        ByVal intestazione As String, _
        ByVal formatoNumero As String) As Variant
    
    
        Dim tipo As String
        Dim prefisso As String
    
        Dim originale As Double
    
    
        tipo = _
            TipoDato( _
                intestazione, _
                formatoNumero)
    
    
        prefisso = _
            PrefissoIntestazione( _
                intestazione)
    
    
        '=====================================================
        ' TESTI SPECIALI
        '=====================================================
    
        Select Case tipo
    
    
            Case "EMAIL"
    
                ValoreSanitizzato = _
                    "utente." & _
                    LCase$(TokenCasuale(8)) & _
                    "@example.com"
    
                Exit Function
    
    
            Case "URL"
    
                ValoreSanitizzato = _
                    "https://example.com/" & _
                    LCase$(TokenCasuale(10))
    
                Exit Function
    
    
            Case "IBAN"
    
                ValoreSanitizzato = _
                    "IT00X" & _
                    CifreCasuali(5) & _
                    CifreCasuali(5) & _
                    TokenCasuale(12)
    
                Exit Function
    
    
            Case "PHONE"
    
                ValoreSanitizzato = _
                    "TEL-" & _
                    CifreCasuali(10)
    
                Exit Function
    
    
            Case "POSTAL"
    
                If IsNumeric(valoreOriginale) Then
    
                    ValoreSanitizzato = _
                        NumeroCasualeStessoSegno( _
                            CDbl(valoreOriginale), _
                            1, _
                            99999)
    
                Else
    
                    ValoreSanitizzato = _
                        CifreCasuali(5)
    
                End If
    
                Exit Function
    
    
        End Select
    
    
        '=====================================================
        ' BOOLEAN
        '=====================================================
    
        If VarType(valoreOriginale) = vbBoolean Then
    
    
            ValoreSanitizzato = _
                (CasualeSicuro(0, 1) = 1)
    
    
            Exit Function
    
    
        End If
    
    
        '=====================================================
        ' NUMERI
        '=====================================================
    
        If IsNumeric(valoreOriginale) Then
    
    
            originale = _
                CDbl(valoreOriginale)
    
    
            If originale = 0 Then
    
                ValoreSanitizzato = 0
    
                Exit Function
    
            End If
    
    
            Select Case tipo
    
    
                Case "DATETIME"
    
    
                    ValoreSanitizzato = _
                        CDbl(DateSerial(2000, 1, 1)) + _
                        CasualeSicuro( _
                            0, _
                            CLng( _
                                Date - _
                                DateSerial(2000, 1, 1))) + _
                        CasualeSicuro( _
                            0, _
                            86399) / _
                        86400#
    
    
                Case "DATE"
    
    
                    ValoreSanitizzato = _
                        CDbl(DateSerial(2000, 1, 1)) + _
                        CasualeSicuro( _
                            0, _
                            CLng( _
                                Date - _
                                DateSerial(2000, 1, 1)))
    
    
                Case "TIME"
    
    
                    ValoreSanitizzato = _
                        CasualeSicuro( _
                            1, _
                            86399) / _
                        86400#
    
    
                Case "DURATION"
    
    
                    ValoreSanitizzato = _
                        NumeroCasualeStessoSegno( _
                            originale, _
                            1, _
                            259200, _
                            86400#)
    
    
                Case "AGE"
    
    
                    ValoreSanitizzato = _
                        NumeroCasualeStessoSegno( _
                            originale, _
                            18, _
                            90)
    
    
                Case "PERCENT"
    
    
                    ValoreSanitizzato = _
                        NumeroCasualeStessoSegno( _
                            originale, _
                            1, _
                            1000000, _
                            1000000#)
    
    
                Case "CURRENCY"
    
    
                    ValoreSanitizzato = _
                        NumeroCasualeStessoSegno( _
                            originale, _
                            1, _
                            100000000, _
                            100#)
    
    
                Case "CODE"
    
    
                    ValoreSanitizzato = _
                        NumeroCasualeStessoSegno( _
                            originale, _
                            100000, _
                            999999)
    
    
                Case Else
    
    
                    If originale <> Fix(originale) Then
    
    
                        ValoreSanitizzato = _
                            NumeroCasualeStessoSegno( _
                                originale, _
                                1, _
                                100000000, _
                                100#)
    
    
                    Else
    
    
                        ValoreSanitizzato = _
                            NumeroCasualeStessoSegno( _
                                originale, _
                                1, _
                                999999)
    
    
                    End If
    
    
            End Select
    
    
            Exit Function
    
    
        End If
    
    
        '=====================================================
        ' TESTO GENERICO
        '=====================================================
    
        If tipo = "CODE" Then
    
    
            ValoreSanitizzato = _
                prefisso & _
                "-" & _
                TokenCasuale(8)
    
    
        Else
    
    
            ValoreSanitizzato = _
                prefisso & _
                "-" & _
                TokenCasuale(4) & _
                "-" & _
                TokenCasuale(4)
    
    
        End If
    
    
    End Function
    
    
    '=========================================================
    ' NUMERO CASUALE CON STESSO SEGNO
    '=========================================================
    
    Private Function NumeroCasualeStessoSegno( _
        ByVal originale As Double, _
        ByVal minimo As Long, _
        ByVal massimo As Long, _
        Optional ByVal divisore As Double = 1#) As Double
    
    
        Dim risultato As Double
    
    
        If originale = 0 Then
    
            NumeroCasualeStessoSegno = 0
    
            Exit Function
    
        End If
    
    
        risultato = _
            CasualeSicuro( _
                minimo, _
                massimo) / _
            divisore
    
    
        If originale > 0 Then
    
    
            NumeroCasualeStessoSegno = _
                risultato
    
    
        Else
    
    
            NumeroCasualeStessoSegno = _
                -risultato
    
    
        End If
    
    
    End Function
    
    
    '=========================================================
    ' VERIFICA FINALE DEL SEGNO
    '=========================================================
    
    Private Function ForzaSegnoCorretto( _
        ByVal valoreOriginale As Variant, _
        ByVal valoreSintetico As Variant, _
        ByVal intestazione As String, _
        ByVal formatoNumero As String) As Variant
    
    
        Dim tipo As String
        Dim originale As Double
        Dim sintetico As Double
    
    
        If Not IsNumeric(valoreOriginale) Then
    
            ForzaSegnoCorretto = _
                valoreSintetico
    
            Exit Function
    
        End If
    
    
        If Not IsNumeric(valoreSintetico) Then
    
            ForzaSegnoCorretto = _
                valoreSintetico
    
            Exit Function
    
        End If
    
    
        tipo = _
            TipoDato( _
                intestazione, _
                formatoNumero)
    
    
        '-----------------------------------------------------
        ' Date e orari esclusi dalla logica del segno
        '-----------------------------------------------------
    
        If _
            tipo = "DATE" Or _
            tipo = "DATETIME" Or _
            tipo = "TIME" Then
    
    
            ForzaSegnoCorretto = _
                valoreSintetico
    
            Exit Function
    
    
        End If
    
    
        originale = _
            CDbl(valoreOriginale)
    
        sintetico = _
            CDbl(valoreSintetico)
    
    
        If originale = 0 Then
    
    
            ForzaSegnoCorretto = 0
    
    
        ElseIf originale > 0 Then
    
    
            If sintetico = 0 Then
    
                ForzaSegnoCorretto = 1
    
            Else
    
                ForzaSegnoCorretto = _
                    Abs(sintetico)
    
            End If
    
    
        Else
    
    
            If sintetico = 0 Then
    
                ForzaSegnoCorretto = -1
    
            Else
    
                ForzaSegnoCorretto = _
                    -Abs(sintetico)
    
            End If
    
    
        End If
    
    
    End Function
    
    
    '=========================================================
    ' IDENTIFICAZIONE TIPO DATO
    '=========================================================
    
    Private Function TipoDato( _
        ByVal intestazione As String, _
        ByVal formatoNumero As String) As String
    
    
        Dim nome As String
    
    
        nome = _
            NormalizzaIntestazione( _
                intestazione)
    
    
        '-----------------------------------------------------
        ' FORMATO EXCEL
        '-----------------------------------------------------
    
        If FormatoPercentuale(formatoNumero) Then
    
            TipoDato = "PERCENT"
            Exit Function
    
        End If
    
    
        If FormatoDurata(formatoNumero) Then
    
            TipoDato = "DURATION"
            Exit Function
    
        End If
    
    
        If FormatoData(formatoNumero) And _
           FormatoOra(formatoNumero) Then
    
            TipoDato = "DATETIME"
            Exit Function
    
        End If
    
    
        If FormatoData(formatoNumero) Then
    
            TipoDato = "DATE"
            Exit Function
    
        End If
    
    
        If FormatoOra(formatoNumero) Then
    
            TipoDato = "TIME"
            Exit Function
    
        End If
    
    
        If FormatoValuta(formatoNumero) Then
    
            TipoDato = "CURRENCY"
            Exit Function
    
        End If
    
    
        '-----------------------------------------------------
        ' INTESTAZIONE
        '-----------------------------------------------------
    
        If _
            Contiene(nome, "email") Or _
            Contiene(nome, "mail") Then
    
            TipoDato = "EMAIL"
            Exit Function
    
        End If
    
    
        If _
            Contiene(nome, "url") Or _
            Contiene(nome, "website") Or _
            Contiene(nome, "sito") Then
    
            TipoDato = "URL"
            Exit Function
    
        End If
    
    
        If Contiene(nome, "iban") Then
    
            TipoDato = "IBAN"
            Exit Function
    
        End If
    
    
        If _
            Contiene(nome, "telefono") Or _
            Contiene(nome, "cellulare") Or _
            Contiene(nome, "phone") Or _
            Contiene(nome, "mobile") Then
    
            TipoDato = "PHONE"
            Exit Function
    
        End If
    
    
        If _
            Contiene(nome, "dataora") Or _
            Contiene(nome, "datetime") Or _
            Contiene(nome, "timestamp") Then
    
            TipoDato = "DATETIME"
            Exit Function
    
        End If
    
    
        If _
            Contiene(nome, "durata") Or _
            Contiene(nome, "duration") Then
    
            TipoDato = "DURATION"
            Exit Function
    
        End If
    
    
        If _
            Contiene(nome, "data") Or _
            Contiene(nome, "date") Then
    
            TipoDato = "DATE"
            Exit Function
    
        End If
    
    
        If _
            nome = "ora" Or _
            nome = "time" Or _
            Contiene(nome, "orario") Then
    
            TipoDato = "TIME"
            Exit Function
    
        End If
    
    
        If _
            nome = "età" Or _
            nome = "eta" Or _
            nome = "age" Then
    
            TipoDato = "AGE"
            Exit Function
    
        End If
    
    
        If _
            nome = "cap" Or _
            Contiene(nome, "postalcode") Or _
            Contiene(nome, "zipcode") Then
    
            TipoDato = "POSTAL"
            Exit Function
    
        End If
    
    
        If _
            Contiene(intestazione, "%") Or _
            Contiene(nome, "percent") Or _
            Contiene(nome, "sconto") Or _
            Contiene(nome, "tasso") Or _
            Contiene(nome, "discount") Or _
            Contiene(nome, "rate") Then
    
            TipoDato = "PERCENT"
            Exit Function
    
        End If
    
    
        If _
            Contiene(nome, "importo") Or _
            Contiene(nome, "prezzo") Or _
            Contiene(nome, "costo") Or _
            Contiene(nome, "ricavo") Or _
            Contiene(nome, "fatturato") Or _
            Contiene(nome, "amount") Or _
            Contiene(nome, "price") Or _
            Contiene(nome, "cost") Or _
            Contiene(nome, "revenue") Then
    
            TipoDato = "CURRENCY"
            Exit Function
    
        End If
    
    
        If _
            nome = "id" Or _
            nome = "code" Or _
            nome = "codice" Or _
            Left$(nome, 2) = "id" Or _
            Right$(nome, 2) = "id" Or _
            Contiene(nome, "codice") Then
    
            TipoDato = "CODE"
            Exit Function
    
        End If
    
    
        TipoDato = _
            "NUMBER"
    
    
    End Function
    
    
    '=========================================================
    ' FORMATI
    '=========================================================
    
    Private Function FormatoPercentuale( _
        ByVal formato As String) As Boolean
    
    
        FormatoPercentuale = _
            Contiene( _
                formato, _
                "%")
    
    
    End Function
    
    
    Private Function FormatoValuta( _
        ByVal formato As String) As Boolean
    
    
        Dim simboloLocale As String
    
    
        On Error Resume Next
    
    
        simboloLocale = _
            CStr( _
                Application.International( _
                    xlCurrencyCode))
    
    
        On Error GoTo 0
    
    
        If _
            Contiene(formato, "€") Or _
            Contiene(formato, "$") Or _
            Contiene(formato, "£") Or _
            Contiene(formato, "¥") Or _
            Contiene(formato, "EUR") Or _
            Contiene(formato, "USD") Or _
            Contiene(formato, "GBP") Or _
            Contiene(formato, "CHF") Or _
            Contiene(formato, "JPY") Then
    
    
            FormatoValuta = True
    
            Exit Function
    
    
        End If
    
    
        If Len(simboloLocale) > 0 Then
    
    
            FormatoValuta = _
                Contiene( _
                    formato, _
                    simboloLocale)
    
    
        End If
    
    
    End Function
    
    
    Private Function FormatoData( _
        ByVal formato As String) As Boolean
    
    
        Dim f As String
    
    
        f = _
            LCase$(formato)
    
    
        FormatoData = _
            InStr(f, "yyyy") > 0 Or _
            InStr(f, "yy") > 0 Or _
            InStr(f, "aaaa") > 0 Or _
            InStr(f, "gg") > 0 Or _
            InStr(f, "dd") > 0 Or _
            InStr(f, "jj") > 0
    
    
    End Function
    
    
    Private Function FormatoOra( _
        ByVal formato As String) As Boolean
    
    
        Dim f As String
    
    
        f = _
            LCase$(formato)
    
    
        FormatoOra = _
            InStr(f, ":") > 0 And _
            ( _
                InStr(f, "h") > 0 Or _
                InStr(f, "ss") > 0 _
            )
    
    
    End Function
    
    
    Private Function FormatoDurata( _
        ByVal formato As String) As Boolean
    
    
        Dim f As String
    
    
        f = _
            LCase$(formato)
    
    
        FormatoDurata = _
            InStr(f, "[h]") > 0 Or _
            InStr(f, "[hh]") > 0 Or _
            InStr(f, "[m]") > 0 Or _
            InStr(f, "[mm]") > 0 Or _
            InStr(f, "[s]") > 0 Or _
            InStr(f, "[ss]") > 0
    
    
    End Function
    
    
    '=========================================================
    ' RANDOM
    '=========================================================
    
    Private Function Casuale32Bit() As Double
    
    
        Dim valore As Long
    
    
    #If Mac Then
    
    
        arc4random_buf _
            valore, _
            LenB(valore)
    
    
    #Else
    
    
        Dim risultato As Long
    
    
        risultato = _
            BCryptGenRandom( _
                0, _
                valore, _
                LenB(valore), _
                BCRYPT_USE_SYSTEM_PREFERRED_RNG)
    
    
        If risultato <> 0 Then
    
    
            Err.Raise _
                vbObjectError + 1000, _
                "Casuale32Bit", _
                "Impossibile generare un numero casuale sicuro."
    
    
        End If
    
    
    #End If
    
    
        If valore < 0 Then
    
    
            Casuale32Bit = _
                CDbl(valore) + _
                4294967296#
    
    
        Else
    
    
            Casuale32Bit = _
                CDbl(valore)
    
    
        End If
    
    
    End Function
    
    
    Private Function CasualeSicuro( _
        ByVal minimo As Long, _
        ByVal massimo As Long) As Long
    
    
        Dim ampiezza As Double
        Dim valore As Double
        Dim limite As Double
        Dim resto As Double
    
    
        If massimo < minimo Then
    
    
            Err.Raise _
                vbObjectError + 1001, _
                "CasualeSicuro", _
                "Intervallo casuale non valido."
    
    
        End If
    
    
        ampiezza = _
            CDbl(massimo) - _
            CDbl(minimo) + _
            1#
    
    
        limite = _
            Fix( _
                4294967296# / _
                ampiezza) * _
            ampiezza
    
    
        Do
    
    
            valore = _
                Casuale32Bit()
    
    
        Loop While valore >= limite
    
    
        resto = _
            valore - _
            Fix( _
                valore / _
                ampiezza) * _
            ampiezza
    
    
        CasualeSicuro = _
            minimo + _
            CLng(resto)
    
    
    End Function
    
    
    Private Function TokenCasuale( _
        ByVal lunghezza As Long) As String
    
    
        Const CARATTERI As String = _
            "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    
    
        Dim risultato As String
        Dim i As Long
    
    
        For i = 1 To lunghezza
    
    
            risultato = _
                risultato & _
                Mid$( _
                    CARATTERI, _
                    CasualeSicuro( _
                        1, _
                        Len(CARATTERI)), _
                    1)
    
    
        Next i
    
    
        TokenCasuale = _
            risultato
    
    
    End Function
    
    
    Private Function CifreCasuali( _
        ByVal lunghezza As Long) As String
    
    
        Dim risultato As String
        Dim i As Long
    
    
        For i = 1 To lunghezza
    
    
            risultato = _
                risultato & _
                CStr( _
                    CasualeSicuro( _
                        0, _
                        9))
    
    
        Next i
    
    
        CifreCasuali = _
            risultato
    
    
    End Function
    
    
    '=========================================================
    ' MAPPING
    '=========================================================
    
    Private Function ChiaveValore( _
        ByVal valore As Variant) As String
    
    
        Dim rappresentazione As String
    
    
        Select Case VarType(valore)
    
    
            Case vbBoolean
    
    
                rappresentazione = _
                    "B|" & _
                    CStr(CBool(valore))
    
    
            Case _
                vbByte, _
                vbInteger, _
                vbLong, _
                vbSingle, _
                vbDouble, _
                vbCurrency, _
                vbDecimal
    
    
                rappresentazione = _
                    "N|" & _
                    Format$( _
                        CDbl(valore), _
                        "0.############################")
    
    
            Case vbDate
    
    
                rappresentazione = _
                    "D|" & _
                    Format$( _
                        CDbl(valore), _
                        "0.############################")
    
    
            Case Else
    
    
                rappresentazione = _
                    "T|" & _
                    CStr(valore)
    
    
        End Select
    
    
        ChiaveValore = _
            ChiaveCompatta( _
                rappresentazione)
    
    
    End Function
    
    
    Private Function ChiaveCompatta( _
        ByVal testo As String) As String
    
    
        Const MODULO As Double = _
            2147483629#
    
    
        Dim h1 As Double
        Dim h2 As Double
    
        Dim codice As Long
        Dim i As Long
    
    
        h1 = 5381#
        h2 = 7919#
    
    
        For i = 1 To Len(testo)
    
    
            codice = _
                AscW( _
                    Mid$( _
                        testo, _
                        i, _
                        1))
    
    
            If codice < 0 Then
    
                codice = _
                    codice + _
                    65536
    
            End If
    
    
            h1 = _
                h1 * 33# + _
                CDbl(codice)
    
    
            h1 = _
                h1 - _
                Fix( _
                    h1 / MODULO) * _
                MODULO
    
    
            h2 = _
                h2 * 65599# + _
                CDbl(codice)
    
    
            h2 = _
                h2 - _
                Fix( _
                    h2 / MODULO) * _
                MODULO
    
    
        Next i
    
    
        ChiaveCompatta = _
            "K" & _
            CStr(Len(testo)) & _
            "_" & _
            Hex$(CLng(h1)) & _
            "_" & _
            Hex$(CLng(h2))
    
    
    End Function
    
    
    Private Function EsisteChiave( _
        ByVal raccolta As Collection, _
        ByVal chiave As String) As Boolean
    
    
        Dim valore As Variant
    
    
        On Error Resume Next
    
        Err.Clear
    
    
        valore = _
            raccolta.Item(chiave)
    
    
        EsisteChiave = _
            (Err.Number = 0)
    
    
        Err.Clear
    
        On Error GoTo 0
    
    
    End Function
    
    
    '=========================================================
    ' UTILITY DATI
    '=========================================================
    
    Private Function ValoreVuoto( _
        ByVal valore As Variant) As Boolean
    
    
        If IsEmpty(valore) Or _
           IsNull(valore) Then
    
    
            ValoreVuoto = True
    
    
        ElseIf Not IsError(valore) Then
    
    
            ValoreVuoto = _
                (CStr(valore) = vbNullString)
    
    
        End If
    
    
    End Function
    
    
    Private Function NormalizzaIntestazione( _
        ByVal intestazione As String) As String
    
    
        Dim risultato As String
    
    
        risultato = _
            LCase$( _
                Trim$(intestazione))
    
    
        risultato = _
            Replace$( _
                risultato, _
                " ", _
                vbNullString)
    
    
        risultato = _
            Replace$( _
                risultato, _
                "_", _
                vbNullString)
    
    
        risultato = _
            Replace$( _
                risultato, _
                "-", _
                vbNullString)
    
    
        NormalizzaIntestazione = _
            risultato
    
    
    End Function
    
    
    Private Function PrefissoIntestazione( _
        ByVal intestazione As String) As String
    
    
        Dim risultato As String
    
    
        risultato = _
            UCase$( _
                NormalizzaIntestazione( _
                    intestazione))
    
    
        If Len(risultato) < 3 Then
    
    
            risultato = _
                risultato & _
                String$( _
                    3 - Len(risultato), _
                    "X")
    
    
        End If
    
    
        PrefissoIntestazione = _
            Left$( _
                risultato, _
                3)
    
    
    End Function
    
    
    Private Function Contiene( _
        ByVal testo As String, _
        ByVal termine As String) As Boolean
    
    
        Contiene = _
            InStr( _
                1, _
                testo, _
                termine, _
                vbTextCompare) > 0
    
    
    End Function
    
    
    '=========================================================
    ' RANGE -> ARRAY 2D
    '=========================================================
    
    Private Function ValoriRange2D( _
        ByVal rng As Range) As Variant
    
    
        Dim valore As Variant
        Dim matrice() As Variant
    
    
        valore = _
            rng.Value2
    
    
        If rng.Cells.CountLarge = 1 Then
    
    
            ReDim matrice(1 To 1, 1 To 1)
    
    
            matrice(1, 1) = _
                valore
    
    
            ValoriRange2D = _
                matrice
    
    
        Else
    
    
            ValoriRange2D = _
                valore
    
    
        End If
    
    
    End Function
    
    
    '=========================================================
    ' CONTA TABELLE
    '=========================================================
    
    Private Function ContaTabelle( _
        ByVal wb As Workbook, _
        ByRef tabellaUnica As ListObject) As Long
    
    
        Dim ws As Worksheet
        Dim tbl As ListObject
        Dim n As Long
    
    
        For Each ws In wb.Worksheets
    
    
            For Each tbl In ws.ListObjects
    
    
                n = n + 1
    
    
                If n = 1 Then
    
                    Set tabellaUnica = _
                        tbl
    
                End If
    
    
            Next tbl
    
    
        Next ws
    
    
        ContaTabelle = _
            n
    
    
    End Function
    
    
    '=========================================================
    ' SELEZIONE TABELLA
    '=========================================================
    
    Private Function SelezionaTabellaDalWorkbook( _
        ByVal wb As Workbook) As ListObject
    
    
        Dim scelta As Range
        Dim tbl As ListObject
    
    
        Do
    
    
            wb.Activate
    
    
            On Error Resume Next
    
    
            Set scelta = _
                Application.InputBox( _
                    Prompt:= _
                        "Il file contiene più Tabelle Excel." & _
                        vbCrLf & vbCrLf & _
                        "Seleziona una cella della tabella " & _
                        "che vuoi sanitizzare.", _
                    Title:="Seleziona la tabella", _
                    Type:=8)
    
    
            On Error GoTo 0
    
    
            If scelta Is Nothing Then
                Exit Function
            End If
    
    
            If Not scelta.Worksheet.Parent Is wb Then
    
    
                MsgBox _
                    "Seleziona una cella nel file da sanitizzare.", _
                    vbExclamation, _
                    "Selezione non valida"
    
    
            Else
    
    
                Set tbl = Nothing
    
    
                On Error Resume Next
    
    
                Set tbl = _
                    scelta.ListObject
    
    
                On Error GoTo 0
    
    
                If tbl Is Nothing Then
    
    
                    MsgBox _
                        "La cella selezionata non appartiene " & _
                        "a una Tabella Excel.", _
                        vbExclamation, _
                        "Selezione non valida"
    
    
                Else
    
    
                    Set SelezionaTabellaDalWorkbook = _
                        tbl
    
    
                    Exit Function
    
    
                End If
    
    
            End If
    
    
            Set scelta = Nothing
    
    
        Loop
    
    
    End Function
    
    
    '=========================================================
    ' NOMI
    '=========================================================
    
    Private Function NomeFileSanitizzato( _
        ByVal nomeFileOriginale As String) As String
    
    
        Dim posizionePunto As Long
        Dim nomeBase As String
    
    
        posizionePunto = _
            InStrRev( _
                nomeFileOriginale, _
                ".")
    
    
        If posizionePunto > 1 Then
    
    
            nomeBase = _
                Left$( _
                    nomeFileOriginale, _
                    posizionePunto - 1)
    
    
        Else
    
    
            nomeBase = _
                nomeFileOriginale
    
    
        End If
    
    
        NomeFileSanitizzato = _
            nomeBase & _
            "_SANITIZED.xlsx"
    
    
    End Function
    
    
    Private Function CreaNomeOutput( _
        ByVal nomeOriginale As String) As String
    
    
        Const SUFFISSO As String = _
            "_SANITIZED"
    
    
        Dim base As String
        Dim lunghezzaBase As Long
    
    
        base = nomeOriginale
    
    
        base = Replace$(base, "\", "_")
        base = Replace$(base, "/", "_")
        base = Replace$(base, ":", "_")
        base = Replace$(base, "*", "_")
        base = Replace$(base, "?", "_")
        base = Replace$(base, "[", "_")
        base = Replace$(base, "]", "_")
        base = Replace$(base, "'", "_")
    
    
        lunghezzaBase = _
            31 - _
            Len(SUFFISSO)
    
    
        base = _
            Left$( _
                base, _
                lunghezzaBase)
    
    
        If Len(base) = 0 Then
            base = "DATA"
        End If
    
    
        CreaNomeOutput = _
            base & _
            SUFFISSO
    
    
    End Function
    
    
    '=========================================================
    ' UTILITY FILE
    '=========================================================
    
    Private Function EstensioneExcelSupportata( _
        ByVal percorso As String) As Boolean
    
    
        Dim posizione As Long
        Dim estensione As String
    
    
        posizione = _
            InStrRev( _
                percorso, _
                ".")
    
    
        If posizione = 0 Then
            Exit Function
        End If
    
    
        estensione = _
            LCase$( _
                Mid$( _
                    percorso, _
                    posizione + 1))
    
    
        Select Case estensione
    
    
            Case _
                "xlsx", _
                "xlsm", _
                "xlsb", _
                "xls"
    
    
                EstensioneExcelSupportata = _
                    True
    
    
        End Select
    
    
    End Function
    
    
    Private Function WorkbookGiaAperto( _
        ByVal percorso As String) As Boolean
    
    
        Dim wb As Workbook
    
    
        For Each wb In _
            Application.Workbooks
    
    
            On Error Resume Next
    
    
            If StrComp( _
                wb.FullName, _
                percorso, _
                vbTextCompare) = 0 Then
    
    
                WorkbookGiaAperto = _
                    True
    
    
                On Error GoTo 0
    
    
                Exit Function
    
    
            End If
    
    
            On Error GoTo 0
    
    
        Next wb
    
    
    End Function
    
    
    Private Function AssicuraEstensioneXlsx( _
        ByVal percorso As String) As String
    
    
        Dim posizionePunto As Long
        Dim posizioneSeparatore As Long
    
        Dim p1 As Long
        Dim p2 As Long
    
    
        If LCase$( _
            Right$( _
                percorso, _
                5)) = ".xlsx" Then
    
    
            AssicuraEstensioneXlsx = _
                percorso
    
    
            Exit Function
    
    
        End If
    
    
        posizionePunto = _
            InStrRev( _
                percorso, _
                ".")
    
    
        p1 = _
            InStrRev( _
                percorso, _
                "\")
    
    
        p2 = _
            InStrRev( _
                percorso, _
                "/")
    
    
        If p1 > p2 Then
    
            posizioneSeparatore = _
                p1
    
        Else
    
            posizioneSeparatore = _
                p2
    
        End If
    
    
        If posizionePunto > posizioneSeparatore Then
    
    
            AssicuraEstensioneXlsx = _
                Left$( _
                    percorso, _
                    posizionePunto - 1) & _
                ".xlsx"
    
    
        Else
    
    
            AssicuraEstensioneXlsx = _
                percorso & _
                ".xlsx"
    
    
        End If
    
    
    End Function
    
    
    Private Function FileEsiste( _
        ByVal percorso As String) As Boolean
    
    
        On Error Resume Next
    
    
        FileEsiste = _
            Len( _
                Dir$(percorso)) > 0
    
    
        On Error GoTo 0
    
    
    End Function
    
    
    '=========================================================
    ' RIPRISTINO EXCEL
    '=========================================================
    
    Private Sub RipristinaApplicazione( _
        ByVal screenUpdating As Boolean, _
        ByVal enableEvents As Boolean, _
        ByVal displayAlerts As Boolean, _
        ByVal automationSecurity As MsoAutomationSecurity)
    
    
        On Error Resume Next
    
    
        Application.StatusBar = False
    
    
        Application.screenUpdating = _
            screenUpdating
    
    
        Application.enableEvents = _
            enableEvents
    
    
        Application.displayAlerts = _
            displayAlerts
    
    
        Application.automationSecurity = _
            automationSecurity
    
    
        On Error GoTo 0
    
    
    End Sub

    Il codice completo lo trovi qui sopra, se ti interessa approfondire.

    Cosa viene mantenuto nel file finale

    Il comportamento cambia in base alla modalità scelta. Se anonimizzamo tutte le colonne, il file risultante contiene dati sintetici per l'intera tabella. Se scegliamo invece soltanto alcune colonne, il risultato sarà una combinazione di valori sintetici e valori originali.

    InformazioneComportamento
    IntestazioniMantenute
    Numero di righeMantenuto
    Numero di colonneMantenuto
    Celle vuoteMantenute
    Colonne selezionateValori sostituiti
    Colonne non selezionateValori originali
    Valori ripetuti nelle colonne anonimizzateMantenuti come ripetizione
    Numeri positiviRimangono positivi
    Numeri negativiRimangono negativi
    ZeroRimane zero
    Formati numericiMantenuti
    Formule originaliNon copiate
    Power QueryNon copiata
    Altri fogliNon copiati
    Mapping originale/sinteticoNon salvato

    La possibilità di lasciare alcune colonne originali è utile quando vogliamo proteggere soltanto una parte del dataset, ma rende ancora più importante controllare il file prima di condividerlo.

    Cosa non viene mantenuto

    La mappatura dei valori viene gestita separatamente per ogni colonna anonimizzata. Questo significa che lo stesso identificativo presente in due colonne differenti non viene necessariamente sostituito con lo stesso valore sintetico.

    Se 12345 compare sia nella colonna ID Cliente sia in Cliente collegato, le due colonne potrebbero ricevere due valori differenti. Se il problema da analizzare dipende proprio dalla relazione tra queste due colonne, sarebbe necessario utilizzare una logica diversa con una mappatura condivisa.

    Allo stesso modo, scegliere di non anonimizzare una colonna significa che i suoi valori originali saranno presenti nel file finale. Non bisogna quindi interpretare il suffisso _SANITIZED come garanzia che qualsiasi informazione contenuta nella tabella sia stata sostituita.

    Questo punto è importante perchè un caso tipico potrebbe essere legato al dover collegare ID in database diversi - visto che la macro punta proprio a creare valori che non siano ripetibili, sarebbe impossibile fare operazioni quali merge di query o simili.

    Anonimizzazione o sanitizzazione?

    In questa guida utilizzo il termine anonimizzare perché è quello più immediato per descrivere ciò che vogliamo ottenere. Tecnicamente dovremmo però parlare di sanitizzazione o di generazione di dati sintetici.

    La macro mantiene infatti intenzionalmente alcune caratteristiche del dataset originale. Se un cliente compare 200 volte, il relativo valore sintetico continuerà a comparire 200 volte; allo stesso modo viene mantenuta la distribuzione tra valori positivi, negativi e zero.

    Inoltre, quando scegliamo di anonimizzare soltanto alcune colonne, il file contiene esplicitamente anche dati originali. Queste caratteristiche sono utili per mantenere il dataset utilizzabile, ma non considererei quindi l'esecuzione della macro come una garanzia automatica di anonimizzazione in senso normativo.

    Cosa controllare prima di condividere il file

    Un file Excel può contenere informazioni anche al di fuori della tabella visibile: fogli nascosti, formule, nomi definiti, commenti, query, connessioni, collegamenti esterni o cache delle PivotTable.

    È proprio per questo che SANITIZE non duplica l'intero workbook. Crea un nuovo file .xlsx e inserisce soltanto la tabella selezionata.

    Prima di condividerlo farei comunque sempre un ultimo controllo, soprattutto se abbiamo scelto di anonimizzare soltanto alcune colonne. Apri il file _SANITIZED.xlsx, verifica le intestazioni e qualche riga e assicurati che tutte le colonne lasciate originali possano effettivamente essere condivise.

    SANITIZE funziona su Windows e Mac?

    La macro è stata strutturata per funzionare sia su Excel desktop per Windows sia su Excel desktop per macOS. La differenza principale riguarda il generatore casuale utilizzato, che viene scelto automaticamente in base al sistema operativo.

    Anche la selezione delle colonne può essere effettuata su entrambe le piattaforme. Per selezionare colonne non adiacenti possiamo utilizzare CTRL su Windows e CMD su Mac.

    Excel Online non esegue VBA, quindi SANITIZE.xlsm deve essere utilizzato tramite l'applicazione desktop di Excel.

    Ammetto - non l'ho testato su MAC, dovrebbe funzionare ma fammi sapere in caso non sia così!

    SANITIZE funziona con tabelle grandi?

    Non ho inserito un limite fisso sul numero di righe o colonne. Le dimensioni vengono lette direttamente dalla Tabella Excel e le matrici utilizzate dalla macro vengono dimensionate dinamicamente.

    Naturalmente rimangono i limiti pratici di Excel, della memoria disponibile e delle prestazioni di VBA. Per questo, oltre i 2 milioni di celle, SANITIZE mostra un avviso prima di procedere. Non è un blocco: possiamo comunque continuare.

    Durante l'elaborazione viene inoltre mostrata la percentuale di avanzamento direttamente nella barra di stato di Excel.

    CONSULENZA EXCEL

    Vuoi applicare quanto visto ai tuoi dati e processi aziendali?

    Se il tuo caso richiede più di un esempio standard, posso aiutarti a progettare o ottimizzare il file e automatizzare le attività ripetitive.

    Marco Filocamo

    Marco Filocamo · Microsoft Excel MVP

    Cerchi invece formazione Excel per il tuo team? Scopri la formazione aziendale

    Conclusioni

    Quando dobbiamo condividere un file Excel per mostrare un problema, una formula o un grafico, spesso non abbiamo bisogno di condividere tutti i dati originali. In alcuni casi vogliamo sostituire l'intera tabella, in altri è sufficiente proteggere soltanto determinate colonne.

    SANITIZE permette entrambe le modalità. Possiamo anonimizzare tutta la tabella oppure selezionare soltanto le colonne da sostituire, mantenendo per quelle scelte valori ripetuti, formati, date, percentuali e segno dei numeri.

    Il file originale viene aperto in sola lettura e il risultato viene creato separatamente come .xlsx. Se lasciamo alcune colonne non anonimizzate, queste vengono riportate nel nuovo file con i valori originali, quindi il controllo finale rimane sempre fondamentale.

    Se invece hai un file Excel o un processo aziendale che vuoi ottimizzare o automatizzare, puoi descrivermi il processo da automatizzare e valutiamo insieme la soluzione più adatta!

    Lascia un commento

    Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

    Excel per Professionisti
    Panoramica privacy

    This website uses cookies so that we can provide you with the best user experience possible. Cookie information is stored in your browser and performs functions such as recognising you when you return to our website and helping our team to understand which sections of the website you find most interesting and useful.