Technical Article

Excel komentari ćelija i hiperlinkovi u Delphi-ju pomoću HotXLS

Preimenujte list iz "Summary" u "Overview" u generisanoj radnoj svesci, i svaki interni hiperlink koji je ukazivao na Summary!A1 prestaje da vodi bilo gde. Nema izuzetaka pri čuvanju, niti pri otvaranju. Link se i dalje prikazuje, i dalje izgleda kao da se može kliknuti, ali tiho ne vodi nikuda. Isti tip kvara se pojavljuje nakon "save-as" konverzije ili kružnog putovanja između .xls i .xlsx formata, kada komentar završi kolonu dalje ili relativni link izgubi svoju ciljnu destinaciju. Obe funkcije nose stanje revizije na osnovu kojeg stvarni ljudi deluju, pa kada se pokvare, taj neuspeh je nevidljiv sve dok korisnik ne klikne i ništa se ne dogodi.

To je praktičan razlog zašto komentari i hiperlinkovi zaslužuju više pažnje nego što to sugeriše njihov kozmetički izgled. HotXLS omogućava Delphi i C++Builder kodu direktan pristup za pisanje i jednih i drugih, kako u XLS tako i u XLSX formatu, bez automatizacije Excel-a. Druga strana te kontrole je odgovornost: biblioteka upisuje tačno one ciljeve koje joj prosledite i ne validira nijedan od njih, pa je održavanje toka rada revizije zadatak vašeg koda, a ne Excel-a.

Komentari ćelija kao mašinski upisani zapisi revizije

U XLSX modelu klasa, komentar je objekat na nivou radnog lista: on poznaje svoj red, svoju kolonu, autora i tekstualno telo. Polje autora ima važnu ulogu. Kada radna sveska koju je vaš kod generisao prođe kroz lanac revizije, prvo pitanje koje revizor postavlja jeste ko je napisao određenu belešku, a beleška ostavljena bez autora na to pitanje odgovara praznim poljem. Označite generisane komentare identitetom servisa kako poreklo nikada ne bi bilo dvosmisleno.

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  Note: TXLSXComment;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('reconciliation.xlsx');
    Sheet := Book.Sheets[0];

    // Authored note on the adjusted figure
    Sheet.AddComment(14, 4, 'Manual adjustment: late FX rate, see ticket FIN-2214',
      'recon-service');

    // Update an existing note instead of stacking a second one
    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;

Metoda FindAt ima veću težinu nego što se to čini. Serijski posao koji se ponovo pokreće nakon privremenog neuspeha rado će pozvati AddComment po drugi put na ćeliji koju je već anotirao, pa ta ćelija završi sa dve naslagane beleške koje niko nije tražio. Najpre izvršite proveru pomoću FindAt, a zatim ažurirajte objekat koji ona vrati. Kolekcija Comments takođe izlaže funkcije DeleteAt i DeleteInRange. Ta varijanta sa opsegom je ona koju treba koristiti kada čistite radnu svesku pre nego što napusti zgradu: uklanjanje internih QA anotacija iz cele regije vrši se jednim pozivom umesto ručno pisane petlje kroz ćelije.

Eksterni URL-ovi i skokovi unutar radne sveske su različiti API-ji

OOXML čuva ove dve vrste linkova na različitim mestima. Eksterni URL postaje unos relacije (relationship) u .rels delu radnog lista, pri čemu ćelija ukazuje na relaciju preko ID-ja. Interni skok uopšte ne dotiče sloj relacija; to je običan string lokacije, kao što je Summary!A1, koji se čuva direktno na samom linku. HotXLS drži tu razliku vidljivom u API-ju umesto da preoptereti jednu metodu, što znači da birate ispravan poziv na osnovu toga gde se cilj nalazi:

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');

Na dobijenom objektu TXLSXHyperlink, svojstva Url i Location se međusobno isključuju, a IsInternal vam govori koje je od ta dva polja popunjeno. Ta zastavica je ono što proveravate kada popisujete linkove u otvorenoj radnoj svesci i kada treba da tretirate eksterne i interne linkove po različitim pravilima: eksterni host bi mogao da se suoči sa listom dozvoljenih adresa, dok interni cilj mora samo da imenuje list koji postoji. Interni linkovi sa sobom ne nose delove relacija, što ih čini lakšim za masovno prepisivanje.

Kvar sa početka priče u potpunosti se dešava na internoj strani, a proizilazi iz jedne činjenice: string lokacije nije parsirana referenca. HotXLS upisuje tačan tekst koji mu prosledite, i ništa ne usmerava taj tekst ponovo kada se list kasnije preimenuje. Dve odbrane su se pokazale uspešnim u praksi. Prva je disciplina oko redosleda: preimenujte svaki list pre nego što generišete ijedan link, a zatim tretirajte nazive listova kao zamrznute identifikatore. Druga je robusnija i preživljava preimenovanja izvršena naknadno. Usmerite link na definisano ime (defined name) na nivou radne sveske umesto na sirovu adresu Sheet!Cell address, jer Excel ponovo upisuje definiciju imena kada se osnovni list promeni, pa se link automatski pomera. Ovaj drugi pristup se prirodno kombinuje sa tehnikama u članku o definisanim imenima i formulama između listova u HotXLS-u.

XLS strana: isti koncepti, starije vodovodne cevi

BIFF8 interfejs vezuje komentare za opsege (ranges) umesto za kolekciju na nivou radnog lista. Pozivate AddComment na IXLSRange objektu i dobijate nazad TXLSComment; svojstvo opsega Comment čita postojeću belešku, a ClearComments ih briše. Oštra ivica ovde je poziciona. Objekat TXLSComment javno ne izlaže sopstveni red i kolonu, pa prirodna petlja za proveru komentara i prijavljivanje gde se oni nalaze ide unazad u odnosu na API. Morate početi od ćelija. Ili vodite proveru na osnovu liste adresa koje ste anotirali, ili vodite sopstveni dnevnik pozicija dok pišete, jer vam objekat komentara kasnije neće reći gde se tačno nalazi.

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;   // pop the note open on first view

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

Postavljanje svojstva Visible na tačno je nasleđeni način da belešku učinite nemogućom za prevideti: žuti okvir ostaje otvoren na listu umesto da čeka da pređete mišem preko njega. TXLSComment ide korak dalje od svog XLSX ekvivalenta izlažući TextRuns, pa jedna beleška može da sadrži podebljano upozorenje pored običnog objašnjenja, što je formatiranje koje XLSX API za komentare ne izlaže na isti način. Hiperlinkovi na ovoj strani dolaze kroz tri progresivna preopterećenja (samo adresa, zatim sa tekstom za prikaz, pa sa kratkim opisom) i čitaju se nazad kroz kolekciju HyperLinks radnog lista, gde svaki link prikazuje Address, SubAddress, DisplayText i ScreenTip.

Indeksni list za reviziju je bolji od razbacanih beleški

Nakon više od desetak anotacija, čitanje prelaženjem mišem prestaje da bude efikasno. Beleške se gomilaju na listovima koje revizor nikada ne otvara, a one koje su najvažnije su upravo one koje se najlakše previde. Struktura koja se najbolje pokazala jeste generisani indeksni list: jedan red po anotiranoj lokaciji, koji navodi naziv lista, adresu ćelije, autora i kratak izvod iz beleške. Poslednja kolona nosi interni hiperlink izgrađen pomoću AddHyperlinkToCell koji skače direktno na anotiranu ćeliju. Sada revizor čita listu odozgo nadole umesto da pretražuje mrežu, a broj redova tog indeksa služi i kao popis komentara za proveru opisanu u nastavku.

Indeks je lak za kreiranje jer vaš generator već zna svaku poziciju koju je dotakao. Dodajte torku (list, red, kolona, autor, rezime) na listu dok pišete svaki komentar, a zatim na kraju generišite indeksni list tako da njegov broj redova bude konačan pre nego što sačuvate fajl. Dva poboljšanja donose veliku korist: sortirajte indeks po važnosti ili po listu, a ne po redosledu unosa, i postavite povratni link u zaglavlje indeksa kako bi revizor mogao da se vrati na vrh nakon svake stavke. Pošto su interni linkovi obični stringovi lokacije bez ikakvih relacija iza sebe, čak i indeks od hiljadu redova skoro uopšte ne utiče na veličinu fajla ili vreme čuvanja.

Taj isti list donosi korist i na povratnom putu. Kada se pregledana radna sveska vrati, vaš kod čita vrednosti statusa upisane u ćelije pored redova indeksa, umesto da ponovo skenira svaki list tražeći komentare koji su se možda promenili. Kolona sa strukturiranim ćelijama statusa se parsira čisto, dok razbacane beleške sa slobodnim tekstom to ne omogućavaju.

Provera pre isporuke koja zaista otkriva kvarove

Nijedan od ovih API-ja ne proverava cilj. Link ka listu koji ste obrisali, pogrešno napisana adresa intraneta, mrežni direktorijum ugašen prošlog kvartala – sve se to čuva bez ikakvih upozorenja. ECMA-376 standard definiše kako se link čuva, a ne da li on uopšte vodi nekuda. Radna sveska koja nosi metapodatke o reviziji zato zahteva kratku fazu provere sa vaše strane, neposredno pre pozivanja SaveAs:

  • Sakupite svaku internu lokaciju upisanu tokom generisanja i potvrdite da naziv lista ispred znaka uzvika i dalje postoji u kolekciji listova radne sveske.
  • Proverite eksterne URL adrese u odnosu na listu dozvoljenih šema i hostova. Obične file:// i UNC putanje otkrivaju detalje o vašem okruženju i prestaju da rade čim fajl napusti vašu mrežu.
  • Prebrojte komentare po listu i uporedite ih sa onim što je vaš generator nameravao da upiše. Ponovni pokušaj koji je udvostručio beleške biće otkriven ovde, a ne u revizorovom sandučetu.
  • Uklonite interne anotacije pomoću DeleteInRange kad god primalac dokumenta sedi van vaše organizacije.

Timovi koji grade svoje radne sveske iz sloja podataka mogu integrisati ovu fazu u isti korak koji već validira podatke, pa provera metapodataka ide besplatno. Mehanika je ista kao ona opisana u članku o izvozu rezultata upita baze podataka u Excel izveštaje, samo usmerena ka linkovima i komentarima umesto ka redovima.

Jedan detalj sa navodnicima može da zbuni ljude kada ručno kreiraju stringove lokacije. List čije ime sadrži razmak mora biti pod navodnicima unutar lokacije, tačno onako kako ga uokviruje linija za formule: 'Quarterly Totals'!A1, a ne Quarterly Totals!A1. HotXLS primenjuje ista pravila koja mehanizam formula koristi za reference između listova, pa ako link radi u formuli radnog lista, njegovi navodnici će raditi i ovde. Prosledite mu nenavođeno ime sa razmakom i dobićete isti nemi mrtav link na koji je početak teksta upozorio.

Komentari i hiperlinkovi su delovi generisane radne sveske na koje revizori reaguju bez prevelikog razmišljanja, što je upravo razlog zašto cilj koji ne vodi nikuda nanosi stvarnu štetu pre nego što iko primeti. Napravite proveru validacije jednom, pokrenite je na svakoj radnoj svesci pre nego što je isporučite, i tok rada revizije će ostati netaknut kroz sva preimenovanja i konverzije. Kompletna API površina za XLS i XLSX interfejse dokumentovana je na stranici proizvoda HotXLS Component.