Technisch artikel

HotXLS: comments, hyperlinks, and review workflows in Delphi

Hernoem een blad van "Summary" naar "Overview" in een gegenereerde werkmap, en elke interne hyperlink die naar Summary!A1 wees, wijst nergens meer naartoe. Geen uitzondering bij opslaan, geen bij openen. De link rendert nog steeds, ziet er nog steeds klikbaar uit, en lost stilletjes op naar niets. Hetzelfde soort defect duikt op na een save-as-conversie of een .xls/.xlsx-round-trip, wanneer een opmerking een kolom te ver terechtkomt of een relatieve link zijn doel verliest. Beide functies dragen reviewstatus waar echte mensen naar handelen, dus wanneer ze breken is de mislukking onzichtbaar totdat een reviewer klikt en er niets gebeurt

Dat is de praktische reden waarom opmerkingen en hyperlinks meer zorg verdienen dan hun cosmetische verschijning doet vermoeden. HotXLS geeft Delphi- en C++Builder-code directe schrijftoegang tot beide, in XLS en XLSX, zonder Excel-automatisering in de lus. De keerzijde van die controle is verantwoordelijkheid: de bibliotheek schrijft precies de doelen die je haar geeft en valideert er geen enkele van, dus het intact houden van een reviewworkflow is de taak van jouw code, niet die van Excel

Celopmerkingen als machinaal geschreven reviewrecords

In het XLSX-klassenmodel is een opmerking een object op werkbladniveau: het kent zijn rij, zijn kolom, een auteur en een tekstinhoud. Het auteursveld verdient zijn plek. Wanneer een werkmap die je code heeft gegenereerd door een reviewketen reist, is de eerste vraag die een auditor stelt wie een gegeven notitie heeft geschreven, en een notitie zonder auteur beantwoordt die vraag met een leegte. Stempel gegenereerde opmerkingen met een service-identiteit zodat de herkomst nooit dubbelzinnig is

Diagram van een Delphi HotXLS-commentaarherhaling waarbij een FindAt-probe de bestaande celnoot bijwerkt terwijl een blinde AddComment-herhaling een duplicaat stapelt
Een nieuwe poging die blind AddComment aanroept stapelt een tweede notitie op dezelfde cel, terwijl de FindAt-probe de notitie bewerkt die er al staat
var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  Note: TXLSXComment;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('reconciliation.xlsx');
    Sheet := Book.Sheets[0];

    // Notitie met auteur bij het aangepaste cijfer
    Sheet.AddComment(14, 4, 'Manual adjustment: late FX rate, see ticket FIN-2214',
      'recon-service');

    // Werk een bestaande notitie bij in plaats van een tweede te stapelen
    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;

De FindAt-probe weegt zwaarder dan hij eruitziet. Een batchtaak die opnieuw probeert na een tijdelijke fout, zal zonder problemen AddComment een tweede keer aanroepen op een cel die al is geannoteerd, en de cel eindigt met twee gestapelde notities waar niemand om heeft gevraagd. Probe eerst met FindAt, en werk het object bij dat het teruggeeft. De Comments-verzameling biedt ook DeleteAt en DeleteInRange. Die bereikvariant is degene om naar te grijpen wanneer je een werkmap saneert voordat die het gebouw verlaat: interne QA-annotaties uit een hele regio wissen is één aanroep in plaats van een handgeschreven lus over cellen

Externe URL's en sprongen binnen de werkmap zijn verschillende API's

OOXML houdt de twee soorten links op verschillende plekken. Een externe URL wordt een relatie-item in het .rels-deel van het blad, waarbij de cel naar de relatie verwijst via een id. Een interne sprong raakt de relatielaag helemaal nooit aan; het is een gewone locatiestring zoals Summary!A1, rechtstreeks op de link opgeslagen. HotXLS houdt dat onderscheid zichtbaar in de API in plaats van één methode te overladen, wat betekent dat je de juiste aanroep kiest door te weten waar het doel leeft:

Diagram dat afzet hoe HotXLS een externe URL opslaat als relationship in het rels-part en een interne sprong als platte locatiestring in door Delphi gegenereerde werkboeken
Een externe URL reist door de relationship-laag terwijl een interne sprong platte tekst is, dus elke soort faalt op zijn eigen manier en heeft zijn eigen audit-regel nodig
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');

Op het resulterende TXLSXHyperlink-object sluiten Url en Location elkaar uit, en IsInternal vertelt je welke van de twee is gevuld. Die vlag is wat je controleert wanneer je de links in een geopende werkmap inventariseert en "verlaat het bestand" en "blijft in het bestand" onder verschillende regels moet behandelen: een externe host krijgt misschien te maken met een allowlist terwijl een intern doel alleen een bestaand blad hoeft te noemen. Interne links dragen geen relatiedelen achter zich, wat ze ook goedkoper maakt om in bulk te herschrijven

De defecten uit de opening zitten volledig aan de interne kant, en dat volgt uit één feit: een locatiestring is geen geparste verwijzing. HotXLS schrijft precies de tekst die je haar geeft, en niets herwijst die tekst wanneer een blad later wordt hernoemd. Twee verdedigingen houden in de praktijk stand. De eerste is discipline over volgorde: hernoem elk blad voordat je ook maar één link genereert, en behandel bladnamen daarna als bevroren identificatoren. De tweede is steviger en overleeft hernoemingen die achteraf gebeuren. Richt de link op een gedefinieerde naam op werkmapniveau in plaats van een ruw Sheet!Cell-adres, want Excel herschrijft de definitie van een naam wanneer het onderliggende blad verandert, dus de link rijdt automatisch mee. Die tweede aanpak past van nature bij de technieken in gedefinieerde namen en cross-sheet-formules in HotXLS

De XLS-kant: dezelfde concepten, oudere leidingen

De BIFF8-facade hangt opmerkingen aan bereiken in plaats van aan een verzameling op werkbladniveau. Je roept AddComment aan op een IXLSRange en krijgt een TXLSComment terug; de Comment-eigenschap van het bereik leest een bestaande notitie, en ClearComments wist ze. Het scherpe randje hier is positioneel. Een TXLSComment stelt zijn eigen rij en kolom niet publiekelijk beschikbaar, dus de natuurlijke lus, "loop door elke opmerking en meld waar hij zit," werkt averechts tegen de API in. Je moet vanaf de cellen beginnen. Stuur de audit ofwel vanuit de lijst adressen die je hebt geannoteerd, of houd je eigen positielogboek bij terwijl je schrijft, want het opmerkingobject vertelt je achteraf niet waar het leeft

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;   // open de notitie meteen bij eerste weergave

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

Visible op True zetten is de legacy manier om een notitie onmogelijk te negeren: het gele vak blijft open op het blad in plaats van te wachten op een hover. TXLSComment gaat een stap verder dan zijn XLSX-tegenhanger door TextRuns bloot te leggen, zodat één notitie een vetgedrukte waarschuwing naast een gewone uitleg kan dragen, opmaak die de XLSX-opmerking-API niet op dezelfde manier biedt. Hyperlinks aan deze kant komen via drie oplopende overloads (alleen adres, dan met weergavetekst, dan met een schermtip) en worden teruggelezen via de HyperLinks-verzameling van het werkblad, waar elke link Address, SubAddress, DisplayText en ScreenTip toont

Een reviewindexblad wint het van verspreide notities

Voorbij een stuk of twaalf annotaties schaalt hover-om-te-lezen stilletjes niet meer mee. Notities stapelen zich op bladen die een reviewer nooit opent, en juist de belangrijkste zijn het makkelijkst te missen. De structuur die het beste standhoudt is een gegenereerd indexblad: één rij per geannoteerde locatie, met bladnaam, celadres, auteur en een kort fragment van de notitie. De laatste kolom draagt een interne hyperlink gebouwd met AddHyperlinkToCell die rechtstreeks naar de geannoteerde cel springt. Nu leest de reviewer een lijst door in plaats van in een raster te jagen, en het rijaantal van die index dient ook als je opmerkinginventaris voor de auditpas hieronder

De index is goedkoop om te bouwen omdat je generator al elke positie kent die hij heeft aangeraakt. Voeg een tuple (blad, rij, kolom, auteur, samenvatting) toe aan een lijst terwijl je elke opmerking schrijft, en genereer het indexblad pas als laatste zodat zijn rijaantal definitief is vóór het opslaan. Twee verfijningen betalen zich uit: sorteer de index op ernst of op blad in plaats van op invoegvolgorde, en zet een terugkeerlink in de indexkop zodat een reviewer na elk item terug naar boven kan springen. Omdat interne links gewone locatiestrings zijn zonder iets erachter in de relatielaag, voegt zelfs een index van duizend rijen vrijwel niets toe aan bestandsgrootte of opslagtijd

Datzelfde blad betaalt zich opnieuw uit op de terugreis. Wanneer de gereviewde werkmap terugkomt, leest je code statuswaarden die in cellen naast de indexrijen zijn getypt, in plaats van elk blad opnieuw te scannen op opmerkingen die mogelijk zijn veranderd. Een kolom gestructureerde statuscellen parseert netjes; een verspreiding van vrije-tekstnotities niet

Een auditpas vóór aflevering die het defect daadwerkelijk vangt

Geen van deze API's valideert een doel. Een link naar een blad dat je hebt verwijderd, een verkeerd gespelde intranethost, een bestandsshare die vorig kwartaal is opgeheven: ze slaan allemaal op zonder een kik. ECMA-376 specificeert hoe een link wordt opgeslagen, niet dat hij ergens naar oplost. Een werkmap die reviewmetadata draagt, verdient daarom een korte eigen auditfase, uitgevoerd vlak vóór SaveAs:

Diagram van de HotXLS-auditpasse vóór levering die interne doelen, URL-allowlists, commentaartellingen en ontvangersopschoning controleert vóór SaveAs in Delphi
Vier controles draaien net vóór SaveAs en elk van hen vangt een fout die de bibliotheek zelf nooit zal gooien
  • Verzamel elke interne locatie die tijdens generatie is geschreven en bevestig dat de bladnaam vóór het uitroepteken nog steeds bestaat in de bladverzameling van de werkmap
  • Controleer externe URL's tegen een allowlist van schema's en hosts. Kale file://- en UNC-paden lekken omgevingsdetails en breken zodra het bestand je netwerk verlaat
  • Tel opmerkingen per blad en vergelijk ze met wat je generator van plan was te schrijven. Een retry die de notities verdubbelde, komt hier aan het licht in plaats van in de inbox van de reviewer
  • Verwijder alleen-interne annotaties met DeleteInRange wanneer de ontvanger buiten de organisatie zit

Teams die hun werkmappen bouwen vanuit een datalaag kunnen deze fase invouwen in dezelfde pipelinestap die de data al valideert, zodat de metadatacontrole gratis meelift. De mechanica is dezelfde als beschreven in databasequeryresultaten exporteren naar Excel-rapporten, maar dan gericht op links en opmerkingen in plaats van op rijen

Eén aanhalingsdetail doet mensen struikelen wanneer ze locatiestrings met de hand bouwen. Een blad waarvan de naam een spatie bevat, moet binnen de locatie tussen aanhalingstekens staan, precies zoals de formulebalk het aanhaalt: 'Quarterly Totals'!A1, niet Quarterly Totals!A1. HotXLS past dezelfde regels toe die de formule-engine gebruikt voor cross-sheet-verwijzingen, dus als een link werkt in een werkbladformule, werkt zijn aanhaling hier ook. Geef het een niet-aangehaalde naam met een spatie en je krijgt dezelfde stille dode link waar de opening voor waarschuwde

Opmerkingen en hyperlinks zijn de onderdelen van een gegenereerde werkmap waar reviewers zonder tweede blik naar handelen, en dat is precies waarom een doel dat nergens naar wijst echte schade aanricht voordat iemand het merkt. Bouw de validatiepas eenmaal, draai hem op elke werkmap voordat die wordt uitgeleverd, en de reviewworkflow blijft intact over hernoemingen en conversies heen. Het volledige API-oppervlak voor zowel de XLS- als de XLSX-facade staat gedocumenteerd op de productpagina van HotXLS Delphi Component