Articolo tecnico

HotXLS: comments, hyperlinks, and review workflows in Delphi

Rinomina un foglio da "Summary" a "Overview" in una cartella di lavoro generata, e ogni collegamento ipertestuale interno che puntava a Summary!A1 smette di andare da qualsiasi parte. Nessuna eccezione al salvataggio, nessuna all'apertura. Il collegamento continua a renderizzarsi, continua a sembrare cliccabile, e silenziosamente risolve verso il nulla. Lo stesso tipo di rottura compare dopo una conversione salva-con-nome o un'andata e ritorno .xls/.xlsx, quando un commento finisce spostato di una colonna o un collegamento relativo perde il suo bersaglio. Entrambe le funzionalità portano stato di revisione su cui agiscono persone reali, quindi quando si rompono il fallimento è invisibile finché un revisore non clicca e non succede nulla

Questo è il motivo pratico per cui commenti e collegamenti ipertestuali meritano più cura di quanto suggerisca il loro aspetto cosmetico. HotXLS dà al codice Delphi e C++Builder accesso diretto in scrittura a entrambi, in XLS e XLSX, senza alcuna automazione di Excel nel ciclo. Il rovescio della medaglia di quel controllo è la responsabilità: la libreria scrive esattamente i bersagli che le passi e non ne convalida nessuno, quindi mantenere intatto un flusso di lavoro di revisione è compito del tuo codice, non di Excel

I commenti di cella come record di revisione scritti a macchina

Nel modello a classi XLSX un commento è un oggetto a livello di foglio di lavoro: conosce la sua riga, la sua colonna, un autore e un corpo di testo. Il campo autore si guadagna il suo posto. Quando una cartella di lavoro generata dal tuo codice attraversa una catena di revisione, la prima domanda che si pone un revisore è chi abbia scritto una data nota, e una nota lasciata senza autore risponde a quella domanda con un vuoto. Timbra i commenti generati con un'identità di servizio così la provenienza non è mai ambigua

Diagramma di un tentativo di commento HotXLS in Delphi in cui una sonda FindAt aggiorna la nota della cella esistente mentre un nuovo tentativo cieco di AddComment impila un duplicato
Un retry che chiama AddComment alla cieca impila una seconda nota sulla stessa cella, mentre la sonda FindAt modifica la nota che è già lì
var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  Note: TXLSXComment;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('reconciliation.xlsx');
    Sheet := Book.Sheets[0];

    // Nota firmata sulla cifra rettificata
    Sheet.AddComment(14, 4, 'Manual adjustment: late FX rate, see ticket FIN-2214',
      'recon-service');

    // Aggiorna una nota esistente invece di accumularne una seconda
    Note := Sheet.Comments.FindAt(14, 4);
    if Note <> nil then
      Note.Text := Note.Text + ' [verified 2026-06-11]';

    Book.SaveAs('reconciliation-reviewed.xlsx');
  finally
    Book.Free;
  end;
end;

La sonda FindAt porta più peso di quanto sembri. Un job batch che ritenta dopo un fallimento transitorio chiamerà volentieri AddComment una seconda volta su una cella già annotata, e la cella finisce con due note accumulate che nessuno ha chiesto. Sonda prima con FindAt, e aggiorna l'oggetto che restituisce. La collezione Comments espone anche DeleteAt e DeleteInRange. Quella variante per intervallo è quella a cui ricorrere quando sanifichi una cartella di lavoro prima che lasci l'edificio: cancellare le annotazioni QA interne da un'intera regione è un'unica chiamata invece di un ciclo scritto a mano sulle celle

URL esterni e salti interni alla cartella di lavoro sono API diverse

OOXML tiene i due tipi di collegamento in posti diversi. Un URL esterno diventa una voce di relazione nella parte .rels del foglio, con la cella che punta alla relazione tramite id. Un salto interno non tocca mai affatto il livello delle relazioni; è una semplice stringa di posizione come Summary!A1 memorizzata direttamente sul collegamento. HotXLS mantiene visibile questa distinzione nell'API invece di sovraccaricare un unico metodo, il che significa che scegli la chiamata giusta sapendo dove vive il bersaglio:

Diagramma che contrappone come HotXLS memorizza un URL esterno come relationship nella parte rels e un salto interno come semplice stringa di posizione nei workbook generati in Delphi
Un URL esterno viaggia attraverso il layer delle relationship mentre un salto interno è testo semplice, così ogni tipo fallisce a modo suo e richiede la propria regola di audit
Sheet.Cells[2, 1].Value := 'Source record';
Sheet.AddHyperlink(2, 1, 'https://intranet.example.com/records/2214',
  'Open record 2214', 'ERP source entry');

Sheet.Cells[3, 1].Value := 'Totals';
Sheet.AddHyperlinkToCell(3, 1, 'Overview!B12', 'Jump to totals');

Sull'oggetto TXLSXHyperlink risultante, Url e Location sono mutuamente esclusivi, e IsInternal ti dice quale dei due è popolato. Questo flag è ciò che controlli quando inventari i collegamenti in una cartella di lavoro aperta e devi trattare "esce dal file" e "resta nel file" sotto regole diverse: un host esterno potrebbe dover passare una allowlist mentre un bersaglio interno deve solo nominare un foglio che esiste. I collegamenti interni non portano parti di relazione dietro di sé, il che li rende anche più economici da riscrivere in blocco

La rottura descritta nell'apertura vive interamente sul lato interno, e segue da un fatto: una stringa di posizione non è un riferimento analizzato. HotXLS scrive esattamente il testo che le passi, e nulla ripunta quel testo quando un foglio viene rinominato in seguito. Due difese reggono nella pratica. La prima è disciplina sull'ordine: rinomina ogni foglio prima di generare un solo collegamento, poi tratta i nomi dei fogli come identificatori congelati. La seconda è più solida e sopravvive a rinomine fatte a posteriori. Punta il collegamento a un nome definito a livello di cartella di lavoro invece che a un indirizzo Sheet!Cell grezzo, perché Excel riscrive la definizione di un nome quando il foglio sottostante cambia, così il collegamento segue automaticamente. Questo secondo approccio si abbina naturalmente alle tecniche in nomi definiti e formule tra fogli in HotXLS

Il lato XLS: stessi concetti, impianto più vecchio

La facciata BIFF8 appende i commenti agli intervalli invece che a una collezione a livello di foglio di lavoro. Chiami AddComment su un IXLSRange e ottieni indietro un TXLSComment; la proprietà Comment dell'intervallo legge una nota esistente, e ClearComments le cancella. Lo spigolo vivo qui è posizionale. Un TXLSComment non espone pubblicamente la propria riga e colonna, quindi il ciclo naturale, "percorri ogni commento e riporta dove si trova," va contro l'API. Devi partire dalle celle. O guidi l'audit dall'elenco degli indirizzi che hai annotato, oppure tieni un tuo registro di posizioni mentre scrivi, perché l'oggetto commento non ti dirà in seguito dove vive

var
  Book: IXLSWorkbook;
  Sheet: IXLSWorksheet;
  Remark: TXLSComment;
begin
  Book := TXLSWorkbook.Create;
  Sheet := Book.Sheets.Add;
  Sheet.Name := 'Review';
  Sheet.Cells.Item[5, 2].Value := 4821.50;

  Remark := Sheet.Cells.Item[5, 2].AddComment('Awaiting sign-off from controller');
  Remark.Visible := True;   // apre la nota alla prima visualizzazione

  Sheet.AddHyperlink(7, 2, 'https://intranet.example.com/signoff/4821',
    'Sign-off form', 'Opens the controller queue');
  Book.SaveAs('review.xls');
end;

Impostare Visible a True è il modo legacy per rendere una nota impossibile da ignorare: il riquadro giallo resta aperto sul foglio invece di aspettare un passaggio del mouse. TXLSComment va un passo oltre la sua controparte XLSX esponendo TextRuns, così una singola nota può portare un avviso in grassetto accanto a una spiegazione semplice, una formattazione che l'API dei commenti XLSX non espone allo stesso modo. I collegamenti ipertestuali su questo lato arrivano tramite tre overload progressivi (solo indirizzo, poi con testo visualizzato, poi con un suggerimento a schermo) e si rileggono tramite la collezione HyperLinks del foglio di lavoro, dove ogni collegamento espone Address, SubAddress, DisplayText e ScreenTip

Un foglio indice di revisione batte le note sparse

Oltre una dozzina di annotazioni circa, il leggi-al-passaggio-del-mouse smette silenziosamente di scalare. Le note si accumulano su fogli che un revisore non apre mai, e quelle che contano di più sono esattamente quelle più facili da perdere. La struttura che ha retto meglio è un foglio indice generato: una riga per posizione annotata, che elenca il nome del foglio, l'indirizzo di cella, l'autore e un breve estratto della nota. L'ultima colonna porta un collegamento ipertestuale interno costruito con AddHyperlinkToCell che salta direttamente alla cella annotata. Ora il revisore legge scorrendo un elenco invece di dare la caccia in una griglia, e il conteggio di righe di quell'indice funge anche da inventario dei commenti per il passaggio di audit qui sotto

L'indice è economico da costruire perché il tuo generatore conosce già ogni posizione che ha toccato. Aggiungi una tupla (foglio, riga, colonna, autore, riepilogo) a una lista mentre scrivi ogni commento, poi emetti il foglio indice per ultimo così il suo conteggio di righe è definitivo prima di salvare. Due rifiniture ripagano: ordina l'indice per gravità o per foglio invece che per sequenza di inserimento, e metti un collegamento di ritorno nell'intestazione dell'indice così un revisore può tornare in cima dopo ogni voce. Poiché i collegamenti interni sono semplici stringhe di posizione senza nulla nel livello delle relazioni dietro di loro, anche un indice di mille righe aggiunge quasi nulla alla dimensione del file o al tempo di salvataggio

Quello stesso foglio ripaga di nuovo al viaggio di ritorno. Quando la cartella di lavoro revisionata torna indietro, il tuo codice legge i valori di stato digitati nelle celle accanto alle righe dell'indice invece di riscansionare ogni foglio alla ricerca di commenti che potrebbero essere cambiati. Una colonna di celle di stato strutturate si analizza in modo pulito; uno spargimento di note in testo libero no

Un passaggio di audit pre-consegna che cattura davvero la rottura

Nessuna di queste API convalida un bersaglio. Un collegamento a un foglio che hai eliminato, un host intranet scritto male, una condivisione file dismessa il trimestre scorso: tutti si salvano senza un mormorio. ECMA-376 specifica come viene memorizzato un collegamento, non che risolva verso qualcosa. Una cartella di lavoro che porta metadati di revisione merita quindi una breve fase di audit tua, eseguita appena prima di SaveAs:

Diagramma della passata di audit pre-consegna di HotXLS che controlla destinazioni interne, allowlist URL, conteggi dei commenti e pulizia dei destinatari prima di SaveAs in Delphi
Quattro controlli girano poco prima di SaveAs e ognuno di essi intercetta un fallimento che la libreria stessa non solleverà mai
  • Raccogli ogni posizione interna scritta durante la generazione e conferma che il nome del foglio prima del punto esclamativo esista ancora nella collezione di fogli della cartella di lavoro
  • Controlla gli URL esterni contro una allowlist di schemi e host. I percorsi file:// nudi e i percorsi UNC rivelano dettagli dell'ambiente e si rompono nel momento in cui il file lascia la tua rete
  • Conta i commenti per foglio e confronta con ciò che il tuo generatore intendeva scrivere. Un retry che ha raddoppiato le note emerge qui invece che nella casella di posta del revisore
  • Rimuovi le annotazioni solo-interne con DeleteInRange ogni volta che il destinatario si trova al di fuori dell'organizzazione

I team che costruiscono le loro cartelle di lavoro a partire da un livello dati possono ripiegare questa fase nello stesso passaggio della pipeline che già convalida i dati, così il controllo dei metadati viaggia gratis. I meccanismi sono quelli descritti in l'esportazione dei risultati di query da database a report Excel, rivolti a collegamenti e commenti invece che a righe

Un dettaglio di quotatura trae in inganno quando si costruiscono stringhe di posizione a mano. Un foglio il cui nome contiene uno spazio deve essere messo tra virgolette dentro la posizione, esattamente come lo mette tra virgolette la barra della formula: 'Quarterly Totals'!A1, non Quarterly Totals!A1. HotXLS applica le stesse regole che il motore di formule usa per i riferimenti tra fogli, quindi se un collegamento funziona in una formula del foglio di lavoro la sua quotatura funzionerà anche qui. Passagli un nome senza virgolette con uno spazio e ottieni lo stesso collegamento morto silenzioso di cui avvertiva l'apertura

Commenti e collegamenti ipertestuali sono le parti di una cartella di lavoro generata su cui i revisori agiscono senza un secondo sguardo, il che è esattamente il motivo per cui un bersaglio che punta al nulla fa danni reali prima che qualcuno se ne accorga. Costruisci il passaggio di convalida una volta, eseguilo su ogni cartella di lavoro prima che venga spedita, e il flusso di lavoro di revisione resta intatto attraverso rinomine e conversioni. L'intera superficie API per entrambe le facciate XLS e XLSX è documentata sulla pagina prodotto HotXLS Delphi Component