Teknisk artikel

Excel-cellekommentarer og hyperlinks i Delphi med HotXLS

Omdøb et ark fra "Summary" til "Overview" i en genereret arbejdsbog, og hvert internt hyperlink, der pegede på Summary!A1, holder op med at føre nogen steder hen. Ingen undtagelse ved gemning, ingen ved åbning. Linket gengives stadig, ser stadig klikbart ud og opløser sig stille til intet. Den samme slags brud viser sig efter en gem-som-konvertering eller en .xls/.xlsx-tur frem og tilbage, når en kommentar lander en kolonne forskudt, eller et relativt link mister sit mål. Begge funktioner bærer review-tilstand, som rigtige mennesker handler på, så når de går i stykker, er fejlen usynlig, indtil en anmelder klikker, og der ikke sker noget

Det er den praktiske grund til, at kommentarer og hyperlinks fortjener mere omhu, end deres kosmetiske fremtoning antyder. HotXLS giver Delphi- og C++Builder-kode direkte skriveadgang til begge, i XLS og XLSX, uden Excel-automatisering i løkken. Bagsiden af den kontrol er ansvar: biblioteket skriver præcis de mål, man giver det, og validerer ingen af dem, så det er ens egen kodes opgave, ikke Excels, at holde en review-workflow intakt

Cellekommentarer som maskinskrevne review-optegnelser

I XLSX-klassemodellen er en kommentar et objekt på regnearksniveau: den kender sin række, sin kolonne, en forfatter og en tekstkrop. Forfatterfeltet fortjener sin plads. Når en arbejdsbog, ens kode har genereret, rejser gennem en review-kæde, er det første spørgsmål, en revisor stiller, hvem der skrev en given note, og en note efterladt uden en forfatter besvarer det spørgsmål med et tomrum. Stempl genererede kommentarer med en serviceidentitet, så herkomsten aldrig er tvetydig

Diagram over et Delphi HotXLS kommentarforsøg igen, hvor en FindAt-prøve opdaterer den eksisterende cellenote, mens et blint AddComment-forsøg stakker en dublet
Et retry, der kalder AddComment blindt, stabler en anden note på samme celle, mens FindAt-proben redigerer den note, der allerede er der
var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  Note: TXLSXComment;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('reconciliation.xlsx');
    Sheet := Book.Sheets[0];

    // Forfattet note på den justerede figur
    Sheet.AddComment(14, 4, 'Manual adjustment: late FX rate, see ticket FIN-2214',
      'recon-service');

    // Opdatér en eksisterende note i stedet for at stable en ny oven på
    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;

FindAt-proben vejer mere, end den ser ud til. Et batch-job, der forsøger igen efter en forbigående fejl, vil glad kalde AddComment en anden gang på en celle, det allerede har annoteret, og cellen ender med to stablede noter, som ingen bad om. Prob med FindAt først, og opdatér det objekt, den returnerer. Comments-samlingen eksponerer også DeleteAt og DeleteInRange. Den range-variant er den, man skal ræk efter, når man renser en arbejdsbog, før den forlader huset: at rydde interne QA-annotationer fra en hel region er ét kald frem for en håndskrevet løkke over celler

Eksterne URL'er og interne hop i arbejdsbogen er forskellige API'er

OOXML holder de to linktyper adskilt. En ekstern URL bliver til en relationship-post i arkets .rels-del, hvor cellen peger på relationen via et id. Et internt hop rører aldrig ved relationship-laget overhovedet; det er blot en location-streng som Summary!A1 gemt direkte på linket. HotXLS holder den skelnen synlig i API'et frem for at overloade en enkelt metode, hvilket betyder, at man vælger det rigtige kald ved at vide, hvor målet bor:

Diagram, der kontrasterer hvordan HotXLS gemmer en ekstern URL som en relationship i rels-delen og et internt spring som en simpel lokationsstreng i Delphi-genererede projektmapper
En ekstern URL rejser gennem relationship-laget, mens et internt hop er ren tekst, så hver slags fejler på sin egen måde og behøver sin egen auditregel
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 resulterende TXLSXHyperlink-objekt er Url og Location gensidigt udelukkende, og IsInternal fortæller, hvilken af de to der er udfyldt. Det flag er, hvad man tjekker, når man gør status over linkene i en åbnet arbejdsbog og skal behandle "forlader filen" og "bliver i filen" under forskellige regler: en ekstern host møder måske en allowlist, mens et internt mål blot skal navngive et ark, der findes. Interne links bærer ingen relationship-dele bag sig, hvilket også gør dem billigere at omskrive i bulk

Bruddet fra indledningen bor udelukkende på den interne side, og det følger af ét faktum: en location-streng er ikke en parset reference. HotXLS skriver præcis den tekst, man giver den, og intet omdirigerer den tekst, når et ark bliver omdøbt senere. To forsvar holder i praksis. Det første er disciplin om rækkefølge: omdøb hvert ark, før man genererer et eneste link, og behandl derefter arknavne som fastfrosne identifikatorer. Det andet er mere robust og overlever omdøbninger foretaget efterfølgende. Peg linket på et arbejdsbogsniveau-defineret navn frem for en rå Sheet!Cell-adresse, fordi Excel omskriver et navns definition, når det underliggende ark ændres, så linket følger automatisk med. Den anden tilgang matcher naturligt teknikkerne i definerede navne og krydsark-formler i HotXLS

XLS-siden: samme koncepter, ældre bagvedliggende mekanik

BIFF8-facaden hænger kommentarer på ranges i stedet for en samling på regnearksniveau. Man kalder AddComment på et IXLSRange og får en TXLSComment tilbage; rangets Comment-egenskab læser en eksisterende note, og ClearComments visker dem ud. Den skarpe kant her er positionel. En TXLSComment eksponerer ikke offentligt sin egen række og kolonne, så den naturlige løkke, "gennemgå hver kommentar og rapportér, hvor den sidder", kører baglæns mod API'et. Man er nødt til at starte fra cellerne. Enten drives audit'en fra listen over adresser, man annoterede, eller også holder man sin egen positionslog, mens man skriver, fordi kommentarobjektet ikke bagefter vil fortælle, hvor 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;   // slå noten op automatisk ved første visning

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

At sætte Visible til True er den gammeldags måde at gøre en note umulig at overse på: den gule boks bliver stående åben på arket i stedet for at vente på en hover. TXLSComment går et skridt videre end sit XLSX-modstykke ved at eksponere TextRuns, så en enkelt note kan bære en fed advarsel ved siden af en almindelig forklaring, formatering XLSX-kommentar-API'et ikke eksponerer på samme måde. Hyperlinks på denne side ankommer gennem tre progressive overloads (kun adresse, så med visningstekst, så med et skærmtip) og læses tilbage via regnearkets HyperLinks-samling, hvor hvert link eksponerer Address, SubAddress, DisplayText og ScreenTip

Et review-indeksark slår spredte noter

Efter et dusin eller flere annotationer holder hover-for-at-læse stille op med at skalere. Noter hober sig op på ark, en anmelder aldrig åbner, og dem, der betyder mest, er præcis dem, der er lettest at overse. Den struktur, der har vist sig bedst, er et genereret indeksark: én række per annoteret placering, der lister dens arknavn, celleadresse, forfatter og et kort uddrag af noten. Den sidste kolonne bærer et internt hyperlink bygget med AddHyperlinkToCell, som hopper direkte til den annoterede celle. Nu læser anmelderen ned ad en liste i stedet for at jage rundt i et gitter, og rækkeantallet i det indeks fungerer også som ens kommentarinventar til audit-passet nedenfor

Indekset er billigt at bygge, fordi ens generator allerede kender hver position, den rørte ved. Tilføj en (ark, række, kolonne, forfatter, resumé)-tupel til en liste, mens man skriver hver kommentar, og udsend derefter indeksarket sidst, så dets rækkeantal er endeligt, før man gemmer. To forfininger betaler sig: sortér indekset efter alvorlighed eller efter ark frem for efter indsættelsesrækkefølge, og læg et returlink i indeksets header, så en anmelder kan hoppe tilbage til toppen efter hvert element. Fordi interne links er blotte location-strenge uden noget i relationship-laget bag sig, tilføjer selv et tusind-rækkers indeks næsten intet til filstørrelse eller gemmetid

Det samme ark betaler sig igen på returrejsen. Når den gennemgåede arbejdsbog kommer tilbage, læser ens kode statusværdier tastet ind i celler ved siden af indeksrækkerne frem for at genscanne hvert ark for kommentarer, der måske har ændret sig. En kolonne af strukturerede statusceller parser rent; et virvar af fritekstnoter gør ikke

Et audit-pas før levering, der rent faktisk fanger bruddet

Ingen af disse API'er validerer et mål. Et link til et ark, man har slettet, en fejlstavet intranet-host, et filshare, der blev nedlagt sidste kvartal: alle gemmer uden en lyd. ECMA-376 specificerer, hvordan et link gemmes, ikke at det opløser sig til noget. En arbejdsbog, der bærer review-metadata, fortjener derfor et kort audit-trin af sin egen, kørt lige før SaveAs:

Diagram over HotXLS pre-delivery auditforløbet, der kontrollerer interne mål, URL allowlister, kommentaroptællinger og modtagerrensning før SaveAs i Delphi
Fire tjek kører lige før SaveAs, og hver eneste af dem opdager en fejl, biblioteket selv aldrig vil udløse
  • Indsaml hver intern location skrevet under generering, og bekræft, at arknavnet foran udråbstegnet stadig findes i arbejdsbogens arksamling
  • Tjek eksterne URL'er mod en allowlist af skemaer og hosts. Blotte file://- og UNC-stier lækker miljødetaljer og går i stykker, i det øjeblik filen forlader dit netværk
  • Tæl kommentarer per ark, og sammenlign med, hvad ens generator havde til hensigt at skrive. Et nyt forsøg, der fordoblede noterne, dukker op her frem for i anmelderens indbakke
  • Fjern kun-interne annotationer med DeleteInRange, hver gang modtageren sidder uden for organisationen

Teams, der bygger deres arbejdsbøger fra et datalag, kan folde dette trin ind i det samme pipeline-trin, der allerede validerer dataene, så metadata-tjekket følger med gratis. Mekanikken er den samme, som beskrives i at eksportere databaseforespørgselsresultater til Excel-rapporter, vendt mod links og kommentarer frem for rækker

Én detalje om anførselstegn snubler folk, når de bygger location-strenge i hånden. Et ark, hvis navn indeholder et mellemrum, skal have anførselstegn inde i location'en, præcis som formellinjen sætter dem: 'Quarterly Totals'!A1, ikke Quarterly Totals!A1. HotXLS anvender de samme regler, som formel-motoren bruger til krydsark-referencer, så hvis et link virker i en regnearksformel, vil dets anførselstegn virke her også. Giv den et navn uden anførselstegn med et mellemrum, og man får det samme tavse døde link, indledningen advarede om

Kommentarer og hyperlinks er de dele af en genereret arbejdsbog, anmeldere handler på uden et sekunds tanke, hvilket er præcis, hvorfor et mål, der peger på intet, gør reel skade, før nogen bemærker det. Byg valideringspasset én gang, kør det på hver arbejdsbog, før den sendes af sted, og review-workflowet forbliver intakt på tværs af omdøbninger og konverteringer. Den fulde API-overflade for både XLS- og XLSX-facaderne er dokumenteret på produktsiden for HotXLS Delphi Component