Definovaný název je popisek zastupující konstantu, rozsah buněk nebo výraz vzorce. V sešitu se ukládá pouze jednou a odkazuje se na něj symbolicky všude, kde je to potřeba. Pokud ve vzorci napíšete TaxRate, jádro jej vyhodnotí na cokoli, co definice názvu obsahuje — ať už je to hodnota 0.08 nebo rozsah Data!$A$2:$D$100. Odkaz napříč listy je související koncept: zápis Data!D2 přistupuje k buňce na jiném listu tím, že adresu kvalifikuje názvem listu. Spojíte-li tyto dvě možnosti dohromady, souhrnný list může sčítat podrobný list pomocí názvu, který se o konkrétní adrese vůbec nezmiňuje, což je přesně to, co požadujete v sešitu, který generátor sestavuje a účetní později kontroluje
HotXLS, nativní knihovna v Delphi od losLab pro soubory XLS a XLSX, zpřístupňuje tabulku názvů obou formátů s možností vytváření, vyhledávání a mazání, a navíc nabízí výpočetní jádro vzorců vyhodnocující názvy a odkazy napříč listy v rámci procesu. Oba formáty si udržují samostatné hierarchie tříd a rozdíly mezi jejich rozhraními API pro názvy jsou tím, co může zkomplikovat přenos kódu z jednoho formátu do druhého
Dvě úložiště názvů bez společného rozhraní
Na straně XLS vrací metoda TXLSWorkbook.GetNames kolekci IXLSNames, jejíž přetížení Add(Name, RefersTo, Visible) zapisuje název do tabulky názvů BIFF. Jednotlivé záznamy se vracejí jako objekty IXLSName nesoucí Name, RefersTo, vyhodnocený RefersToRange a metodu Delete. Na straně XLSX představuje vlastnost TXLSXWorkbook.DefinedNames kolekci TXLSXDefinedNames s metodami Add, FindByName a DeleteByName
Konvence vyhledávání se liší způsobem, který se projeví spíše při převodu kódu za běhu než při kompilaci. Výchozí vlastnost Item kolekce XLS přijímá typ Variant, takže se vůči ní vyhodnocují jak Names[0], tak Names['TaxRate']. Kolekce XLSX žádnou takovou výchozí vlastnost nemá; voláte metodu FindByName('TaxRate'), která v případě absence názvu vrátí hodnotu nil. Kód napsaný pro jedno rozhraní se vůči druhému zkompiluje pouze náhodou a chyba se pak projeví spíše jako přístup k neexistujícímu objektu (nil access) za běhu než jako červené podtržení v IDE
Rozsah platnosti je prvním rozhodnutím, nikoli příznakem přidávaným později
Definovaný název má platnost buď pro celý sešit (workbook-scoped), kdy je viditelný pro vzorce na všech listech, nebo pro konkrétní list (sheet-scoped), kdy je viditelný pouze na svém domovském listu. V rozhraní API XLSX představuje tento rozdíl jediný volitelný parametr. Volání DefinedNames.Add(AName, AFormula) vytvoří název na úrovni sešitu, zatímco Add(AName, AFormula, ASheetIndex) jej naváže na konkrétní list. Při zpětném čtení vrací vlastnost TXLSXDefinedName.SheetIndex hodnotu -1 pro platnost v celém sešitu, v opačném případě vrací index listu od nuly
Rozsah platnosti zároveň slouží jako vaše strategie předcházení kolizím, což je důvod, proč si jej ujasnit ještě před zápisem prvního názvu. Excel povoluje lokální název Total na každém listu a zároveň globální název Total pro celý sešit. Vzorec na daném listu pak vyhodnocuje lokální název prioritně. Generované sešity by toho měly záměrně využívat. Globální parametry využívané více listy, jako jsou daňové sazby, měnové kurzy a sledované období, patří do rozsahu sešitu. Pomocné rozsahy, na které odkazují pouze vzorce jednoho listu, je bezpečnější definovat s platností pouze pro daný list, kde nemohou nic zastínit a nic nezastíní je
var
Book: TXLSXWorkbook;
Data, Summary: TXLSXWorksheet;
begin
Book := TXLSXWorkbook.Create;
try
Data := Book.Sheets.Add('Data');
Summary := Book.Sheets.Add('Summary');
// ... fill Data!A2:D100 with detail rows ...
Book.DefinedNames.Add('TaxRate', '0.08'); // workbook scope, a constant
Book.DefinedNames.Add('DataBlock', 'Data!$A$2:$D$100'); // workbook scope, a range
Book.DefinedNames.Add('LocalNote', 'Summary!$B$1', 1); // scoped to sheet index 1 only
// XLSX formulas take no leading '='
Summary.Cells[2, 2].Formula := 'SUM(Data!D2:D100)*TaxRate';
Book.SaveAs('model.xlsx');
finally
Book.Free;
end;
end;
Definovaný název nemusí ukazovat pouze na rozsah buněk. Výše uvedený TaxRate odkazuje na pouhou konstantu 0.08, což je nejčistší způsob, jak definovat globální parametry. Zobrazí se jednou ve Správci názvů Excelu, každý vzorec na něj symbolicky odkazuje a změna sazby v příštím čtvrtletí představuje úpravu jednoho řádku v generátoru namísto vyhledávání ve čtrnácti sestavených řetězcích vzorců
Znaménko rovná se, které patří pouze na jednu stranu
Způsob zadávání vzorců je místem, kde se přenášený kód nejčastěji rozbíjí, protože obě rozhraní se neshodnou na používání znaménka rovná se. Buňky XLS přijímají vzorce přes vlastnost Value se začínajícím =. Buňky XLSX mají vyhrazenou vlastnost Formula, která přijímá výraz bez této předpony. Pokud zapíšete '=SUM(A1:A10)' do vlastnosti TXLSXCell.Formula, znaménko rovná se se stane součástí uloženého textu výrazu namísto označení vzorce a soubor se nebude chovat tak, jak se choval stejný řetězec na straně XLS
var
Book: IXLSWorkbook; // interface-counted: do not Free
Names: IXLSNames;
begin
Book := TXLSWorkbook.Create;
// assume a sheet named 'Data' already holds the detail rows
Names := Book.GetNames;
Names.Add('TaxRate', '0.08');
Names.Add('Helper', 'Data!$A$2:$A$100', False); // False = hidden from the Name Manager
// XLS formulas go through Value, with the '=' prefix
Book.Sheets[1].Cells.Item[2, 2].Value := '=SUM(Data!A2:A100)*TaxRate';
Book.SaveAs('model.xls');
end;
Tato ukázka odhaluje další dvě specifika strany XLS. Kolekce listů začíná indexem 1, takže Sheets[1] je první list, na rozdíl od indexace od nuly u XLSX Sheets[0]. A třetí parametr metody Add vytváří skrytý název: ten je v souboru přítomen a vzorce jej mohou používat, ale je neviditelný ve Správci názvů Excelu. Skryté názvy jsou správným nástrojem pro vnitřní logiku generátoru, kterou by koncoví uživatelé neměli omylem upravovat ani mazat
Odkazy napříč listy a co se stane při přesunu řádků
Obě výpočetní jádra vzorců přijímají standardní syntaxi napříč listy. Jednoduché názvy listů se kvalifikují přímo jako Data!A1; název s mezerami nebo interpunkcí vyžaduje jednoduché uvozovky, například 'Sheet With Space'!A1. V textu RefersTo u definovaného názvu sáhněte téměř vždy po absolutních odkazech, jako je Data!$A$2:$D$100. Relativní odkaz uvnitř definovaného názvu se vyhodnocuje relativně k buňce, která jej používá, což je záměrná vlastnost Excelu a spolehlivý zdroj zmatků, pokud se aktivuje omylem
Strukturální úpravy jsou chvílí, kdy se správa odkazů napříč listy vyplatí, a strana XLSX udržuje názvy v těchto situacích konzistentní. Metody InsertRows a DeleteRows posouvají definované rozsahy názvů společně s buňkami, sloučeními, hypertextovými odkazy i kotvami grafů. Název ukazující na Data!$A$2:$D$100 tak stále pokrývá datový blok i poté, co generátor otevře mezeru nad ním. U vzorců je třeba pamatovat na jedno zdokumentované upozornění: vložení řádku upravuje pouze ty odkazy, které cílí na upravovaný list. Vzorec na listu Summary odkazující na Data!D2:D100 se přepíše při vložení řádků na list Data, což je obvykle požadované chování. Raději si to však ověřte, než na to spoléhejte, protože jádro vám to sdělí velmi jednoduše:
// the calculation engine resolves names and cross-sheet references in-process
V := Book.Calculate('SUM(Data!D2:D100)*TaxRate');
if VarIsNumeric(V) then
Log('net total checks out: ' + FloatToStr(V));
Metoda Calculate vyhodnocuje libovolný výraz vůči aktuálnímu stavu sešitu bez nutnosti cokoli ukládat, což z ní dělá ideální kontrolní prvek pro testy generátoru. Vypočítejte očekávaný souhrn ze zdrojových dat v Pascalu, vyhodnoťte vlastní vzorec sešitu a oba výsledky porovnejte. Článek o jádru vzorců popisuje, co a kdy jádro vyhodnocuje a jak jej rozšířit o vlastní funkce
Názvy _xlnm patřící vrstvě vlastností
Pokud otevřete tabulku názvů generovaného souboru v nízkoúrovňovém inspektoru, najdete záznamy, které jste nikdy nezapsali: _xlnm.Print_Area, _xlnm.Print_Titles a jim podobné. Tímto způsobem formát OOXML (ECMA-376 / ISO 29500) ukládá tiskové oblasti a opakované řádky záhlaví — jako definované názvy s vyhrazenými identifikátory. HotXLS je spravuje prostřednictvím vyhrazených vlastností listu, takže nastavení PrintArea nebo PrintTitleRows zapíše odpovídající položku _xlnm.* za vás
Úskalím je ruční zasahování do tohoto vyhrazeného jmenného prostoru. Pokud přidáte položku _xlnm.Print_Area přes DefinedNames.Add a zároveň nastavíte vlastnost PrintArea, sešit bude obsahovat dvě konfliktní definice pro jeden vyhrazený název. Excel tento stav řeší způsoby, na které by se žádný produkt neměl spoléhat. S každým identifikátorem začínajícím na _xlnm. zacházejte jako s prvkem patřícím vrstvě vlastností. Chcete-li zkontrolovat nastavení tisku, čtěte vlastnosti, nikoli tabulku názvů. Článek o zabezpečení a nastavení stránky se těmto vlastnostem tiskové oblasti věnuje v kontextu
Dva limity, které je dobré znát před dokončením návrhu
Definované názvy se nepřenášejí přes pohodlný převodní most z XLS do XLSX. Metoda SaveXLSWorkbookAsXLSX kopíruje obsah buněk a základní formátování, tabulka názvů však není na jejím dokumentovaném seznamu kopírování. Sešit, který na svých názvů závisel, je při převodu ztratí. Po převodu názvy znovu vytvořte pomocí DefinedNames.Add. Tento krok je snazší, než se zdá, protože vám dává prostor pro normalizaci jejich rozsahů platnosti namísto pouhého přenesení toho, co se v souboru XLS zrovna nacházelo
Druhým limitem je nesoulad mezi řetězci vzorců a názvy listů. Excel při interaktivním přejmenování přepisuje odkazy na listy uvnitř vzorců a názvů, takže soubory upravované uživatelem v Excelu zůstávají samy o sobě konzistentní. Riziko je na straně generátoru: když kód v Pascalu sestavuje řetězce vzorců z doslovného názvu listu, jeho přejmenování na jednom místě a opomenutí na druhém vytvoří odkaz na list, který již neexistuje. Udržujte název listu v jediné konstantě v Delphi a předávejte ji jak metodě Sheets.Add, tak sestavování vzorců — tyto dvě části se pak nikdy neshodnou. Je to stejný princip, který hovoří pro pojmenovávání výstupních buněk reportu namísto pevného kódování adres: šablona, jejíž buňka s celkovým součtem je pojmenovaná, funguje i poté, co návrhář vloží tři řádky nad ni, zatímco generátor zapisující na pevnou adresu B17 tiše zapíše číslo na špatné místo. Článek o generování reportů ze šablon staví přesně na tomto vzoru
Kompletní API definovaných názvů pro oba formáty, společně s referencí výpočetního jádra vzorců, se dodává s produktem HotXLS Component