Technický článek

Komentáře buněk a odkazy v Excelu v Delphi s HotXLS

Přejmenujte list z „Summary" na „Overview" ve vygenerovaném sešitu a každý interní hypertextový odkaz, který mířil na Summary!A1, přestane vést kamkoli. Žádná výjimka při ukládání, žádná při otevření. Odkaz se stále vykreslí, stále vypadá jako klikatelný a tiše se přeloží na nic. Stejný typ poškození se objeví i po konverzi typu save-as nebo po cyklu .xls/.xlsx tam a zpět, když se komentář posune o sloupec vedle nebo relativní odkaz ztratí svůj cíl. Obě funkce nesou revizní stav, na jehož základě jednají skuteční lidé, takže když se pokazí, selhání zůstane neviditelné, dokud recenzent na odkaz neklikne a nic se nestane

To je praktický důvod, proč si komentáře a hypertextové odkazy zaslouží více péče, než naznačuje jejich kosmetický vzhled. HotXLS dává kódu v Delphi a C++Builderu přímý zápisový přístup k oběma prvkům, v XLS i XLSX, bez jakékoli automatizace Excelu v řetězci. Odvrácenou stranou této kontroly je zodpovědnost: knihovna zapíše přesně ty cíle, které jí předáte, a žádný z nich neověřuje, takže udržet revizní workflow neporušený je úkolem vašeho kódu, ne Excelu

Komentáře buněk jako strojově psané revizní záznamy

V modelu tříd XLSX je komentář objekt na úrovni listu: zná svůj řádek, svůj sloupec, autora a textový obsah. Pole autora si své místo zaslouží. Když sešit vygenerovaný vaším kódem prochází revizním řetězcem, první otázka, kterou si auditor položí, zní, kdo danou poznámku napsal, a poznámka ponechaná bez autora na tuto otázku odpovídá prázdným polem. Označte generované komentáře servisní identitou, aby původ nebyl nikdy nejednoznačný

Diagram opakování komentáře HotXLS v Delphi, kde sonda FindAt aktualizuje existující poznámku buňky, zatímco slepé opakování AddComment naskládá duplikát
Opakování, které slepo volá AddComment, navrství druhou poznámku na tutéž buňku, zatímco sonda FindAt upraví poznámku, která už tam je
var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  Note: TXLSXComment;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('reconciliation.xlsx');
    Sheet := Book.Sheets[0];

    // Podepsaná poznámka k upravené hodnotě
    Sheet.AddComment(14, 4, 'Manual adjustment: late FX rate, see ticket FIN-2214',
      'recon-service');

    // Aktualizace stávající poznámky místo přidání druhé
    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 má větší váhu, než se na první pohled zdá. Dávková úloha, která se po dočasném selhání zopakuje, klidně zavolá AddComment podruhé na buňku, kterou už okomentovala, a buňka pak skončí se dvěma poznámkami navrch sebe, o které nikdo nežádal. Nejprve prozkoumejte pomocí FindAt a aktualizujte vrácený objekt. Kolekce Comments také nabízí DeleteAt a DeleteInRange. K variantě pro rozsah sáhněte ve chvíli, kdy sešit před odesláním čistíte: vymazání interních QA poznámek z celé oblasti je jedno volání místo ručně psané smyčky přes buňky

Externí URL a skoky uvnitř sešitu jsou odlišná API

OOXML udržuje oba typy odkazů na různých místech. Externí URL se stane záznamem vztahu (relationship) v části .rels daného listu, přičemž buňka na tento vztah odkazuje přes id. Interní skok se vrstvy vztahů vůbec nedotkne; jde o obyčejný řetězec umístění, například Summary!A1, uložený přímo na odkazu. HotXLS udržuje tento rozdíl viditelný v API místo toho, aby přetěžoval jedinou metodu, což znamená, že správné volání vyberete podle toho, kde cíl žije:

Diagram kontrastující, jak HotXLS ukládá externí URL jako relationship v části rels a interní skok jako prostý location řetězec v sešitech generovaných v Delphi
Externí URL cestuje vrstvou relationship, zatímco interní skok je holý text, takže každý druh selhává po svém a potřebuje vlastní auditní pravidlo
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 výsledném objektu TXLSXHyperlink se Url a Location vzájemně vylučují a IsInternal vám řekne, která z těchto dvou vlastností je vyplněná. Tento příznak kontrolujete ve chvíli, kdy inventarizujete odkazy v otevřeném sešitu a potřebujete podle odlišných pravidel zacházet s tím, co „opouští soubor" a co „zůstává v souboru": externí hostitel může podléhat allowlistu, zatímco interní cíl musí jen pojmenovat list, který existuje. Interní odkazy za sebou nenesou žádné části vztahů, což je také činí levnějšími na hromadné přepsání

Poškození z úvodu žije celé na interní straně a plyne z jednoho faktu: řetězec umístění není naparsovaná reference. HotXLS zapíše přesně ten text, který mu předáte, a nic tento text znovu nepřesměruje, když je list později přejmenován. V praxi obstojí dvě obrany. První je disciplína v pořadí: přejmenujte každý list dřív, než vygenerujete jediný odkaz, a poté zacházejte s názvy listů jako se zmrazenými identifikátory. Druhá je odolnější a přežije i přejmenování provedená dodatečně. Nasměrujte odkaz na definovaný název na úrovni sešitu místo na syrovou adresu Sheet!Cell, protože Excel přepíše definici názvu, když se podkladový list změní, takže se odkaz automaticky sveze s ním. Tento druhý přístup se přirozeně pojí s technikami popsanými v článku o definovaných názvech a vzorcích napříč listy v HotXLS

Strana XLS: stejné koncepty, starší technické zázemí

Fasáda BIFF8 zavěšuje komentáře na rozsahy místo na kolekci na úrovni listu. Zavoláte AddComment na objektu IXLSRange a dostanete zpět TXLSComment; vlastnost Comment daného rozsahu čte existující poznámku a ClearComments je vymaže. Ostrá hrana je tu poziční. Objekt TXLSComment veřejně nezpřístupňuje svůj vlastní řádek a sloupec, takže přirozená smyčka „projdi každý komentář a nahlaš, kde sedí" jde proti srsti tomuto API. Musíte začít od buněk. Buď řiďte audit ze seznamu adres, které jste okomentovali, nebo si při zápisu veďte vlastní záznam pozic, protože objekt komentáře vám později neřekne, kde žije

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;   // otevře poznámku hned při prvním zobrazení

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

Nastavení Visible na True je starší způsob, jak zajistit, že poznámka nejde přehlédnout: žlutý rámeček zůstane na listu otevřený místo toho, aby čekal na najetí myší. TXLSComment jde o krok dál než jeho protějšek v XLSX tím, že zpřístupňuje TextRuns, takže jediná poznámka může nést tučné varování vedle prostého vysvětlení, formátování, které API komentářů XLSX stejným způsobem nezpřístupňuje. Hypertextové odkazy na této straně přicházejí přes tři postupná přetížení (jen adresa, poté s zobrazovaným textem, poté s tipem obrazovky) a čtou se zpět přes kolekci HyperLinks listu, kde každý odkaz zpřístupňuje Address, SubAddress, DisplayText a ScreenTip

List s revizním indexem poráží roztroušené poznámky

Po zhruba tuctu poznámek přestává princip „najeď a přečti" tiše škálovat. Poznámky se hromadí na listech, které recenzent nikdy neotevře, a právě ty nejdůležitější jsou nejsnáze přehlédnutelné. Struktura, která se osvědčila nejlépe, je generovaný indexový list: jeden řádek na každé okomentované místo, s uvedením názvu listu, adresy buňky, autora a krátkého výtahu z poznámky. Poslední sloupec nese interní hypertextový odkaz vytvořený pomocí AddHyperlinkToCell, který skočí přímo na okomentovanou buňku. Recenzent pak čte seznam odshora dolů místo pátrání po mřížce, a počet řádků tohoto indexu zároveň slouží jako inventář vašich komentářů pro auditní krok níže

Index je levné sestavit, protože váš generátor už zná každou pozici, které se dotkl. Při zápisu každého komentáře přidejte do seznamu n-tici (list, řádek, sloupec, autor, shrnutí) a indexový list vydejte až jako poslední, aby byl počet jeho řádků před uložením konečný. Vyplatí se dvě vylepšení: seřaďte index podle závažnosti nebo podle listu místo podle pořadí vkládání a do záhlaví indexu vložte návratový odkaz, aby se recenzent mohl po každé položce vrátit nahoru. Protože interní odkazy jsou obyčejné řetězce umístění bez čehokoli ve vrstvě vztahů za sebou, i tisícařádkový index přidá k velikosti souboru nebo době ukládání téměř nic

Tentýž list se vyplatí znovu i na cestě zpět. Když se recenzovaný sešit vrátí, váš kód čte hodnoty stavu zapsané do buněk vedle řádků indexu, místo aby znovu procházel každý list a hledal komentáře, které se možná změnily. Sloupec strukturovaných stavových buněk se parsuje čistě; roztroušené poznámky ve volném textu ne

Auditní krok před předáním, který poškození skutečně odhalí

Žádné z těchto API cíl neověřuje. Odkaz na list, který jste smazali, překlepnutý název intranetového hostitele, sdílená složka zrušená minulé čtvrtletí: to vše se uloží bez jediného protestu. ECMA-376 specifikuje, jak je odkaz uložen, ne to, že se na něco přeloží. Sešit, který nese revizní metadata, si proto zaslouží vlastní krátký auditní krok, spuštěný těsně před SaveAs:

Diagram auditního průchodu HotXLS před doručením, který před SaveAs v Delphi zkontroluje interní cíle, URL allowlisty, počty komentářů a čištění příjemců
Čtyři kontroly běží těsně před SaveAs a každá z nich chytí selhání, které knihovna sama nikdy nevyvolá
  • Shromážděte každé interní umístění zapsané během generování a ověřte, že název listu před vykřičníkem stále existuje v kolekci listů sešitu
  • Zkontrolujte externí URL proti allowlistu schémat a hostitelů. Holé cesty file:// a UNC prozrazují detaily prostředí a přestanou fungovat ve chvíli, kdy soubor opustí vaši síť
  • Spočítejte komentáře na list a porovnejte je s tím, co váš generátor zamýšlel zapsat. Opakování, které poznámky zdvojilo, se odhalí tady, a ne až v poštovní schránce recenzenta
  • Odstraňte pouze interní poznámky pomocí DeleteInRange vždy, když příjemce sedí mimo organizaci

Týmy, které staví sešity z datové vrstvy, mohou tento krok sloučit se stejným krokem pipeline, který už validuje data, takže se kontrola metadat sveze zdarma. Mechanika je stejná jako ta popsaná v článku o exportu výsledků databázových dotazů do reportů v Excelu, jen zaměřená na odkazy a komentáře místo na řádky

Jeden detail s uvozovkami lidem podráží nohy, když si řetězce umístění sestavují ručně. List, jehož název obsahuje mezeru, musí být uvnitř umístění uzavřen do uvozovek přesně tak, jak to dělá řádek vzorců: 'Quarterly Totals'!A1, ne Quarterly Totals!A1. HotXLS uplatňuje stejná pravidla, jaká pro odkazy napříč listy používá jádro vzorců, takže pokud odkaz funguje ve vzorci na listu, jeho uvozovkování bude fungovat i tady. Předejte mu neuzavřený název s mezerou a dostanete stejný tichý mrtvý odkaz, před kterým varoval úvod

Komentáře a hypertextové odkazy jsou části vygenerovaného sešitu, na které recenzenti jednají bez druhého pohledu, a přesně proto cíl, který nikam nemíří, napáchá reálnou škodu dřív, než si toho kdokoli všimne. Vytvořte validační krok jednou, spouštějte jej na každém sešitu před odesláním a revizní workflow zůstane neporušený napříč přejmenováními i konverzemi. Celý povrch API pro fasády XLS i XLSX je zdokumentován na stránce produktu HotXLS Delphi Component