Artykuł techniczny

Komentarze i hiperłącza Excela w Delphi z HotXLS

Zmień nazwę arkusza z "Summary" na "Overview" w generowanym skoroszycie, a każde wewnętrzne hiperłącze wskazujące na Summary!A1 przestaje gdziekolwiek prowadzić. Żadnego wyjątku przy zapisie, żadnego przy otwarciu. Link nadal się renderuje, nadal wygląda na klikalny i po cichu rozwiązuje się w nic. Ta sama awaria pojawia się po konwersji zapisz-jako albo po podróży .xls/.xlsx w obie strony, gdy komentarz ląduje o kolumnę obok albo względny link gubi swój cel. Obie funkcje niosą stan recenzji, na podstawie którego działają prawdziwi ludzie, więc kiedy się psują, awaria jest niewidoczna aż do chwili, gdy recenzent klika i nic się nie dzieje

To praktyczny powód, dla którego komentarze i hiperłącza zasługują na więcej uwagi, niż sugeruje ich kosmetyczny wygląd. HotXLS daje kodowi Delphi i C++Buildera bezpośredni dostęp do zapisu obu, w XLS i XLSX, bez automatyzacji Excela w pętli. Odwrotną stroną tej kontroli jest odpowiedzialność: biblioteka zapisuje dokładnie te cele, które jej podasz, i nie waliduje żadnego z nich, więc utrzymanie nienaruszonego przepływu recenzji to zadanie twojego kodu, nie Excela

Komentarze w komórkach jako maszynowo pisane zapisy recenzji

W modelu klas XLSX komentarz jest obiektem na poziomie arkusza: zna swój wiersz, swoją kolumnę, autora i treść tekstu. Pole autora zasługuje na swoje miejsce. Kiedy skoroszyt wygenerowany przez twój kod wędruje przez łańcuch recenzji, pierwsze pytanie audytora brzmi, kto napisał daną notatkę, a notatka zostawiona bez autora odpowiada na to pytanie pustką. Stemplaj generowane komentarze tożsamością usługi, żeby pochodzenie nigdy nie było niejednoznaczne

Diagram ponowienia komentarza HotXLS w Delphi, w którym sonda FindAt aktualizuje istniejącą notatkę w komórce, podczas gdy ślepe ponowienie AddComment nakłada duplikat
Ponowienie, które ślepo wywołuje AddComment, nakłada drugą notatkę na tę samą komórkę, podczas gdy sonda FindAt edytuje notatkę, która już tam jest
var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  Note: TXLSXComment;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('reconciliation.xlsx');
    Sheet := Book.Sheets[0];

    // Notatka z autorem przy skorygowanej wartości
    Sheet.AddComment(14, 4, 'Manual adjustment: late FX rate, see ticket FIN-2214',
      'recon-service');

    // Zaktualizuj istniejącą notatkę zamiast nakładać drugą
    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;

Sonda FindAt waży więcej, niż na to wygląda. Zadanie wsadowe, które ponawia próbę po przejściowej awarii, chętnie wywoła AddComment drugi raz na komórce, którą już opatrzyło notatką, i komórka kończy z dwiema nałożonymi notatkami, o które nikt nie prosił. Najpierw sonduj przez FindAt i aktualizuj obiekt, który zwróci. Kolekcja Comments wystawia też DeleteAt i DeleteInRange. Ten wariant zakresowy jest tym, po który sięgasz, gdy oczyszczasz skoroszyt przed jego wysłaniem poza firmę: usunięcie wewnętrznych adnotacji QA z całego obszaru to jedno wywołanie, a nie ręcznie pisana pętla po komórkach

Zewnętrzne adresy URL i skoki wewnątrz skoroszytu to różne API

OOXML trzyma oba rodzaje linków w różnych miejscach. Zewnętrzny adres URL staje się wpisem relacji w części .rels arkusza, a komórka wskazuje na relację przez jej identyfikator. Wewnętrzny skok w ogóle nie dotyka warstwy relacji; to zwykły ciąg lokalizacji, taki jak Summary!A1, przechowywany wprost na linku. HotXLS utrzymuje to rozróżnienie widoczne w API, zamiast przeciążać jedną metodę, co znaczy, że właściwe wywołanie wybierasz, wiedząc, gdzie mieszka cel:

Diagram zestawiający sposób, w jaki HotXLS przechowuje zewnętrzny adres URL jako relację w części rels, a wewnętrzny skok jako zwykły ciąg lokalizacji w skoroszytach generowanych w Delphi
Zewnętrzny adres URL przechodzi przez warstwę relacji, a wewnętrzny skok jest zwykłym tekstem, więc każdy rodzaj psuje się na swój sposób i potrzebuje własnej reguły audytu
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 powstałym obiekcie TXLSXHyperlink pola Url i Location wzajemnie się wykluczają, a IsInternal mówi ci, które z dwóch jest wypełnione. Tę flagę sprawdzasz, gdy inwentaryzujesz linki w otwartym skoroszycie i musisz potraktować "wychodzi z pliku" i "zostaje w pliku" według różnych reguł: zewnętrzny host może stanąć przed listą dozwolonych, podczas gdy cel wewnętrzny musi tylko nazwać arkusz, który istnieje. Wewnętrzne linki nie niosą za sobą żadnych części relacji, co czyni je też tańszymi w masowym przepisywaniu

Awaria z otwarcia artykułu żyje w całości po stronie wewnętrznej i wynika z jednego faktu: ciąg lokalizacji nie jest sparsowaną referencją. HotXLS zapisuje dokładnie ten tekst, który mu podasz, i nic nie przekierowuje tego tekstu, gdy arkusz zostanie później przemianowany. W praktyce sprawdzają się dwie obrony. Pierwsza to dyscyplina kolejności: zmień nazwy wszystkich arkuszy, zanim wygenerujesz choćby jeden link, a potem traktuj nazwy arkuszy jak zamrożone identyfikatory. Druga jest solidniejsza i przeżywa zmiany nazw dokonane po fakcie. Skieruj link na nazwę zdefiniowaną na poziomie skoroszytu zamiast na surowy adres Sheet!Cell, bo Excel przepisuje definicję nazwy, gdy zmienia się arkusz pod spodem, więc link jedzie razem z nią automatycznie. To drugie podejście naturalnie łączy się z technikami z artykułu nazwy zdefiniowane i formuły międzyarkuszowe w HotXLS

Strona XLS: te same pojęcia, starsza hydraulika

Fasada BIFF8 wiesza komentarze na zakresach zamiast na kolekcji na poziomie arkusza. Wywołujesz AddComment na IXLSRange i dostajesz z powrotem TXLSComment; właściwość Comment zakresu czyta istniejącą notatkę, a ClearComments je kasuje. Ostra krawędź jest tutaj pozycyjna. TXLSComment nie wystawia publicznie własnego wiersza i kolumny, więc naturalna pętla "przejdź po każdym komentarzu i zgłoś, gdzie siedzi" działa pod prąd API. Musisz zacząć od komórek. Albo prowadź audyt z listy adresów, które opatrzyłeś notatkami, albo trzymaj własny log pozycji w trakcie zapisu, bo obiekt komentarza nie powie ci potem, gdzie mieszka

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;   // otwórz notatkę od razu przy pierwszym spojrzeniu

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

Ustawienie Visible na True to starszy sposób, by notatka była nie do przeoczenia: żółte pole zostaje otwarte na arkuszu zamiast czekać na najechanie kursorem. TXLSComment idzie krok dalej niż jego odpowiednik z XLSX, bo wystawia TextRuns, więc pojedyncza notatka może nieść pogrubione ostrzeżenie obok zwykłego wyjaśnienia — formatowanie, którego API komentarzy XLSX nie wystawia w ten sam sposób. Hiperłącza po tej stronie przychodzą przez trzy narastające przeciążenia (sam adres, potem z tekstem wyświetlanym, potem z dymkiem podpowiedzi) i odczytuje się je przez kolekcję HyperLinks arkusza, gdzie każdy link wystawia Address, SubAddress, DisplayText i ScreenTip

Arkusz indeksu recenzji bije rozproszone notatki

Powyżej kilkunastu adnotacji czytanie przez najechanie kursorem po cichu przestaje się skalować. Notatki piętrzą się na arkuszach, których recenzent nigdy nie otwiera, a te najważniejsze są dokładnie tymi, które najłatwiej przeoczyć. Strukturą, która trzyma się najlepiej, jest generowany arkusz indeksu: jeden wiersz na adnotowaną lokalizację, z nazwą arkusza, adresem komórki, autorem i krótkim fragmentem notatki. Ostatnia kolumna niesie wewnętrzne hiperłącze zbudowane przez AddHyperlinkToCell, które skacze prosto do adnotowanej komórki. Teraz recenzent czyta listę w dół zamiast polować po siatce, a liczba wierszy tego indeksu służy zarazem za inwentarz komentarzy do przebiegu audytu opisanego niżej

Indeks jest tani w budowie, bo twój generator zna już każdą pozycję, której dotknął. Dopisuj krotkę (arkusz, wiersz, kolumna, autor, streszczenie) do listy przy zapisie każdego komentarza, a arkusz indeksu emituj na końcu, żeby liczba jego wierszy była ostateczna przed zapisem. Dwa dopracowania się opłacają: uporządkuj indeks według wagi albo według arkusza, a nie według kolejności wstawiania, i umieść link powrotny w nagłówku indeksu, żeby recenzent mógł odbić się na górę po każdej pozycji. Ponieważ wewnętrzne linki to zwykłe ciągi lokalizacji bez niczego w warstwie relacji za nimi, nawet tysiącwierszowy indeks prawie nic nie dodaje do rozmiaru pliku ani do czasu zapisu

Ten sam arkusz opłaca się ponownie w drodze powrotnej. Kiedy zrecenzowany skoroszyt wraca, twój kod czyta wartości statusu wpisane do komórek obok wierszy indeksu, zamiast ponownie skanować każdy arkusz w poszukiwaniu komentarzy, które mogły się zmienić. Kolumna ustrukturyzowanych komórek statusu parsuje się czysto; rozsypka swobodnych notatek tekstowych nie

Przebieg audytu przed dostawą, który naprawdę łapie awarię

Żadne z tych API nie waliduje celu. Link do arkusza, który usunąłeś, źle wpisany host intranetowy, udział plikowy wycofany w zeszłym kwartale: wszystko to zapisuje się bez szemrania. ECMA-376 określa, jak link jest przechowywany, a nie to, że do czegokolwiek się rozwiązuje. Skoroszyt niosący metadane recenzji zasługuje więc na krótki własny etap audytu, uruchamiany tuż przed SaveAs:

Diagram przebiegu audytu HotXLS przed dostawą, który sprawdza cele wewnętrzne, listy dozwolonych adresów URL, liczby komentarzy i czyszczenie pod odbiorcę przed SaveAs w Delphi
Cztery kontrole biegną tuż przed SaveAs i każda z nich łapie awarię, której sama biblioteka nigdy nie zgłosi
  • Zbierz każdą wewnętrzną lokalizację zapisaną podczas generowania i potwierdź, że nazwa arkusza przed wykrzyknikiem nadal istnieje w kolekcji arkuszy skoroszytu
  • Sprawdź zewnętrzne adresy URL wobec listy dozwolonych schematów i hostów. Gołe ścieżki file:// i UNC wyciekają szczegóły środowiska i psują się w chwili, gdy plik opuszcza twoją sieć
  • Policz komentarze na arkusz i porównaj z tym, co twój generator zamierzał zapisać. Ponowienie, które podwoiło notatki, wychodzi tutaj, a nie w skrzynce recenzenta
  • Usuwaj adnotacje wyłącznie wewnętrzne przez DeleteInRange, ilekroć odbiorca siedzi poza organizacją

Zespoły, które budują skoroszyty z warstwy danych, mogą wpiąć ten etap w ten sam krok potoku, który już waliduje dane, więc kontrola metadanych jedzie razem za darmo. Mechanika jest tą opisaną w artykule eksport wyników zapytań bazodanowych do raportów Excela, obrócona ku linkom i komentarzom zamiast ku wierszom

Jeden szczegół cytowania podkłada ludziom nogę, gdy budują ciągi lokalizacji ręcznie. Arkusz, którego nazwa zawiera spację, musi być ujęty w apostrofy wewnątrz lokalizacji, dokładnie tak, jak ujmuje go pasek formuły: 'Quarterly Totals'!A1, a nie Quarterly Totals!A1. HotXLS stosuje te same reguły, których silnik formuł używa do referencji międzyarkuszowych, więc jeśli link działa w formule arkusza, jego cytowanie zadziała i tutaj. Podaj mu nieujętą nazwę ze spacją, a dostaniesz ten sam cichy martwy link, przed którym ostrzegało otwarcie

Komentarze i hiperłącza to te części generowanego skoroszytu, na których recenzenci działają bez zastanowienia, i właśnie dlatego cel wskazujący w nicość robi realną szkodę, zanim ktokolwiek zauważy. Zbuduj przebieg walidacji raz, uruchamiaj go na każdym skoroszycie, zanim wyruszy, a przepływ recenzji przetrwa zmiany nazw i konwersje. Pełna powierzchnia API dla obu fasad, XLS i XLSX, jest udokumentowana na stronie produktu HotXLS Delphi Component