Teknisk artikel

HotXLS: comments, hyperlinks, and review workflows in Delphi

Byt namn på ett blad från "Summary" till "Overview" i en genererad arbetsbok, och varje intern hyperlänk som pekade på Summary!A1 slutar leda någonstans. Inget undantag vid sparning, inget vid öppning. Länken renderas fortfarande, ser fortfarande klickbar ut, och löses tyst till ingenting. Samma sorts trasighet dyker upp efter en spara-som-konvertering eller en tur-och-retur mellan .xls/.xlsx, när en kommentar hamnar en kolumn fel eller en relativ länk tappar sitt mål. Båda funktionerna bär granskningstillstånd som verkliga människor agerar på, så när de går sönder är felet osynligt tills en granskare klickar och ingenting händer

Det är den praktiska anledningen till att kommentarer och hyperlänkar förtjänar mer omsorg än deras kosmetiska utseende antyder. HotXLS ger Delphi- och C++Builder-kod direkt skrivåtkomst till båda, i XLS och XLSX, utan Excel-automation i loopen. Baksidan av den kontrollen är ansvar: biblioteket skriver exakt de mål du ger det och validerar inget av dem, så att hålla ett granskningsarbetsflöde intakt är din kods jobb, inte Excels

Cellkommentarer som maskinskrivna granskningsposter

I XLSX-klassmodellen är en kommentar ett objekt på kalkylbladsnivå: den känner till sin rad, sin kolumn, en författare och en textkropp. Författarfältet förtjänar sin plats. När en arbetsbok din kod genererade färdas genom en granskningskedja är den första frågan en granskare ställer vem som skrev en given anteckning, och en anteckning lämnad utan författare svarar på den frågan med ett tomrum. Stämpla genererade kommentarer med en tjänsteidentitet så att härkomsten aldrig är oklar

Diagram över ett Delphi HotXLS-kommentarförsök där en FindAt-sondering uppdaterar den befintliga cellnoten, medan ett blint AddComment-försök staplar en dubblett
Ett nytt försök som anropar AddComment blindly staplar en andra anteckning på samma cell, medan FindAt-sonderingen redigerar anteckningen som redan finns där
var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  Note: TXLSXComment;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('reconciliation.xlsx');
    Sheet := Book.Sheets[0];

    // Signerad anteckning om det justerade beloppet
    Sheet.AddComment(14, 4, 'Manual adjustment: late FX rate, see ticket FIN-2214',
      'recon-service');

    // Uppdatera en befintlig anteckning i stället för att stapla en andra
    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;

Sonderingen med FindAt bär mer tyngd än den ser ut att göra. Ett batchjobb som gör om ett anrop efter ett övergående fel kommer glatt att anropa AddComment en andra gång på en cell det redan har kommenterat, och cellen hamnar med två staplade anteckningar som ingen bad om. Sondera med FindAt först, och uppdatera objektet den returnerar. Samlingen Comments exponerar också DeleteAt och DeleteInRange. Den områdesvarianten är den att ta till när du saniterar en arbetsbok innan den lämnar byggnaden: att rensa interna QA-anteckningar från ett helt område är ett enda anrop i stället för en handskriven loop över celler

Externa URL:er och hopp inom arbetsboken är olika API:er

OOXML håller de två länktyperna på olika platser. En extern URL blir en relationspost i bladets .rels-del, med cellen pekande på relationen via ett id. Ett internt hopp rör aldrig relationslagret alls; det är en ren platssträng som Summary!A1 lagrad direkt på länken. HotXLS håller den distinktionen synlig i API:et i stället för att överbelasta en enda metod, vilket betyder att du väljer rätt anrop genom att veta var målet bor:

Diagram som kontrasterar hur HotXLS lagrar en extern URL som en relationship i rels-delen, och ett internt hopp som en enkel platssträng i Delphi-genererade arbetsböcker
En extern URL färdas genom relationslagret medan ett internt hopp är ren text, så varje sort misslyckas på sitt eget sätt och behöver sin egen granskningsregel
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');

På det resulterande TXLSXHyperlink-objektet är Url och Location ömsesidigt uteslutande, och IsInternal talar om för dig vilken av de två som är ifylld. Den flaggan är vad du kontrollerar när du inventerar länkarna i en öppnad arbetsbok och behöver behandla "lämnar filen" och "stannar i filen" under olika regler: en extern värd kan möta en tillåtelselista medan ett internt mål bara behöver namnge ett blad som existerar. Interna länkar bär inga relationsdelar bakom sig, vilket också gör dem billigare att skriva om i bulk

Trasigheten från inledningen bor helt på den interna sidan, och den följer av ett faktum: en platssträng är inte en tolkad referens. HotXLS skriver exakt den text du ger den, och inget pekar om den texten när ett blad döps om senare. Två försvar håller i praktiken. Det första är disciplin kring ordning: döp om varje blad innan du genererar en enda länk, behandla sedan bladnamn som frysta identifierare. Det andra är stadigare och överlever namnbyten gjorda i efterhand. Peka länken mot ett definierat namn på arbetsboksnivå snarare än en rå Blad!Cell-adress, eftersom Excel skriver om ett namns definition när det underliggande bladet ändras, så länken följer med automatiskt. Det andra tillvägagångssättet passar naturligt ihop med teknikerna i definierade namn och korsbladsformler i HotXLS

XLS-sidan: samma koncept, äldre rörledningar

BIFF8-fasaden hänger kommentarer på områden i stället för en samling på kalkylbladsnivå. Du anropar AddComment på ett IXLSRange och får tillbaka ett TXLSComment; områdets egenskap Comment läser en befintlig anteckning, och ClearComments raderar dem. Den skarpa kanten här är positionell. Ett TXLSComment exponerar inte publikt sin egen rad och kolumn, så den naturliga loopen, "gå igenom varje kommentar och rapportera var den sitter," körs baklänges mot API:et. Du måste starta från cellerna. Antingen driv granskningen från listan över adresser du kommenterade, eller håll en egen positionslogg medan du skriver, för kommentarsobjektet kommer inte att tala om för dig efteråt var det bor

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;   // fäll upp anteckningen direkt vid första visningen

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

Att sätta Visible till True är det äldre sättet att göra en anteckning omöjlig att missa: den gula rutan förblir öppen på bladet i stället för att vänta på en hover. TXLSComment går ett steg längre än sin XLSX-motsvarighet genom att exponera TextRuns, så att en enda anteckning kan bära en fetstilt varning bredvid en vanlig förklaring, formatering som XLSX-kommentars-API:et inte exponerar på samma sätt. Hyperlänkar på den här sidan anländer genom tre stegrande överlagringar (bara adress, sedan med visningstext, sedan med en skärmtips) och läses tillbaka via kalkylbladets samling HyperLinks, där varje länk visar Address, SubAddress, DisplayText och ScreenTip

Ett granskningsindexblad slår utspridda anteckningar

Förbi ett dussintal anteckningar eller så slutar hovra-för-att-läsa tyst att skala. Anteckningar hopar sig på blad en granskare aldrig öppnar, och de som spelar mest roll är precis de lättast att missa. Strukturen som har hållit bäst är ett genererat indexblad: en rad per kommenterad plats, som listar dess bladnamn, celladress, författare och ett kort utdrag av anteckningen. Den sista kolumnen bär en intern hyperlänk byggd med AddHyperlinkToCell som hoppar direkt till den kommenterade cellen. Nu läser granskaren nedåt i en lista i stället för att jaga över ett rutnät, och det indexets radantal fungerar samtidigt som ditt kommentarinventarium för granskningspasset nedan

Indexet är billigt att bygga eftersom din generator redan känner till varje position den rörde. Lägg till en (blad, rad, kolumn, författare, sammanfattning)-tupel i en lista när du skriver varje kommentar, skriv sedan ut indexbladet sist så att dess radantal är slutgiltigt innan du sparar. Två förfiningar lönar sig: ordna indexet efter allvarlighetsgrad eller efter blad snarare än infogningsordning, och lägg en returlänk i indexrubriken så att en granskare kan studsa tillbaka till toppen efter varje post. Eftersom interna länkar är rena platssträngar utan något i relationslagret bakom sig, lägger även ett index med tusen rader till nästan ingenting i filstorlek eller sparningstid

Samma blad lönar sig igen på returresan. När den granskade arbetsboken kommer tillbaka läser din kod statusvärden skrivna i celler bredvid indexraderna i stället för att skanna om varje blad efter kommentarer som kan ha ändrats. En kolumn med strukturerade statusceller tolkas rent; en spridning av fritextanteckningar gör det inte

Ett förleveransgranskningspass som faktiskt fångar trasigheten

Ingen av de här API:erna validerar ett mål. En länk till ett blad du tog bort, en felstavad intranätvärd, en filresurs avvecklad förra kvartalet: alla sparas utan ett knyst. ECMA-376 specificerar hur en länk lagras, inte att den löser sig till något. En arbetsbok som bär granskningsmetadata förtjänar därför ett eget kort granskningssteg, kört precis före SaveAs:

Diagram över HotXLS:s granskningspass före leverans som kontrollerar interna mål, URL-tillståndslistor, kommentarsräkningar och mottagarrengöring före SaveAs i Delphi
Fyra kontroller körs precis före SaveAs och var och en av dem fångar ett fel biblioteket själv aldrig kommer att kasta
  • Samla in varje intern plats skriven under genereringen och bekräfta att bladnamnet före utropstecknet fortfarande finns i arbetsbokens bladsamling
  • Kontrollera externa URL:er mot en tillåtelselista av scheman och värdar. Rena file://- och UNC-sökvägar läcker miljödetaljer och går sönder i samma stund filen lämnar ditt nätverk
  • Räkna kommentarer per blad och jämför mot vad din generator avsåg att skriva. Ett omförsök som dubblerade anteckningarna dyker upp här i stället för i granskarens inkorg
  • Ta bort enbart interna anteckningar med DeleteInRange när mottagaren befinner sig utanför organisationen

Team som bygger sina arbetsböcker från ett datalager kan vika in det här steget i samma pipelinesteg som redan validerar datan, så metadatakontrollen följer med gratis. Mekaniken är samma som beskrivs i artikeln om att exportera databasfrågeresultat till Excel-rapporter, vänd mot länkar och kommentarer i stället för rader

En citeringsdetalj snubblar folk när de bygger platssträngar för hand. Ett blad vars namn innehåller ett mellanslag måste citeras inuti platssträngen, exakt som formelfältet citerar det: 'Quarterly Totals'!A1, inte Quarterly Totals!A1. HotXLS tillämpar samma regler formelmotorn använder för korsbladsreferenser, så om en länk fungerar i en kalkylbladsformel kommer dess citering att fungera här också. Ge den ett ociterat namn med ett mellanslag och du får samma tysta döda länk inledningen varnade om

Kommentarer och hyperlänkar är de delarna av en genererad arbetsbok som granskare agerar på utan en andra blick, vilket är precis varför ett mål som pekar på ingenting gör verklig skada innan någon märker det. Bygg valideringspasset en gång, kör det på varje arbetsbok innan den levereras, och granskningsarbetsflödet förblir intakt över namnbyten och konverteringar. Hela API-ytan för både XLS- och XLSX-fasaderna är dokumenterad på produktsidan för HotXLS Delphi Component