Technical Article

Ochrana listu XLSX v Delphi: 15 možností Allow

Předáte hotový sešit kolegovi a chcete, aby v něm mohl filtrovat, ne ho přepisovat. Tak list ochráníte. Ve starších verzích HotXLS tenhle úkon zapisoval do souboru jednu věc: <sheetProtection sheet="1" objects="1" scenarios="1"/>, natvrdo, pokaždé. List se uzamkl, připojil se hash hesla a uživatel nemohl dělat vůbec nic, ani třídit a filtrovat, které jste ve skutečnosti chtěli ponechat otevřené. Samotný dialog Excelu "Protect Sheet" má z tohoto důvodu patnáct zaškrtávacích políček a engine neuměl vyjádřit ani jedno z nich. Právě tuhle mezeru uzavírá model ochrany ve verzi v2.91.0

HotXLS je nativní VCL komponenta pro tabulky pro Delphi a C++Builder, která umí číst a zapisovat XLS i XLSX bez instalovaného Excelu. Tento článek se věnuje části ochrany listů v XLSX: novému TXLSXSheetProtectionOption enumu, vlastnosti AllowOption property, která přepíná jednotlivá oprávnění, a jednomu pravidlu kódování OOXML, na kterém pohoří každý, kdo zapisuje prvek <sheetProtection> ručně

Co ochrana listu vlastně chrání

Nejprve hranice, protože ta rozhoduje o tom, jak moc tomu můžete věřit. Ochrana listu ve formátu tabulek OOXML (ECMA-376) je zásada pro interakci, ne šifrování. Říká kompatibilní aplikaci, které úpravy má při chráněném listu odmítnout. Hodnoty buněk přitom dál leží v xl/worksheets/sheetN.xml v čistém textu; rozbalte .xlsx a jsou tam přímo. Volitelné heslo je uložené jako krátký historický hash, ne jako klíč, který by něco šifroval. Kdokoli přejmenuje soubor, otevře část a odstraní řádek <sheetProtection> si vše přečte i upraví

Ochrana tedy odpovídá na otázku "zastav kolegu, aby omylem nepřepsal vzorec", ne na otázku "udrž tato data v tajnosti před někým odhodlaným". To jsou různé problémy a používají se na ně různé nástroje. Pokud potřebujete důvěrnost, chcete šifrování na úrovni sešitu popsané v Výstup XLSX chráněný AES, které balíček skutečně zašifruje. Ochrana listu a šifrování sešitu se dají kombinovat čistě, ale zámek je jen to druhé. Když tohle držíte oddělené, zbytek stránky je už jen technická instalace

Patnáct možností a vlastnost AllowOption

Každý list teď nese sadu hodnot TXLSXSheetProtectionOption popisujících, co uživatel smí dělat, když je list chráněný. Jednotlivé členy se mapují jedna ku jedné na atributy OOXML i na zaškrtávací políčka dialogu Excelu:

  • xlsxSpoEditObjects, xlsxSpoEditScenarios - upravovat kreslené objekty a scénáře what-if
  • xlsxSpoFormatCells, xlsxSpoFormatColumns, xlsxSpoFormatRows - formátovat buňky, sloupce, řádky
  • xlsxSpoInsertColumns, xlsxSpoInsertRows, xlsxSpoInsertHyperlinks - vkládat sloupce, řádky, odkazy
  • xlsxSpoDeleteColumns, xlsxSpoDeleteRows - mazat sloupce, řádky
  • xlsxSpoSelectLockedCells, xlsxSpoSelectUnlockedCells - přesouvat výběr na zamknuté nebo odemknuté buňky
  • xlsxSpoSort, xlsxSpoAutoFilter, xlsxSpoPivotTables - řadit oblasti, používat rozbalovací nabídky AutoFilter, pracovat s kontingenčními tabulkami

Jednotlivé bity čtete a zapisujete přes indexovanou vlastnost AllowOption na TXLSXWorksheet. AllowOption[Opt] = True znamená, že je akce povolená, nastavení False ji zakáže. Celá sada je zároveň dostupná najednou přes SheetProtectionOptions, TXLSXSheetProtectionOptions (prostý Pascal set of), takže ji můžete uložit, obnovit nebo nahradit jako celek

Výchozí stav je důležitý a je záměrný: nově vytvořený list začíná s povolenou každou možností. Konstruktor nastaví SheetProtectionOptions na celý rozsah, [Low(TXLSXSheetProtectionOption)..High(TXLSXSheetProtectionOption)]. Odtud už jen omezujete akce, které chcete zakázat, místo toho, abyste od nuly skládali sadu oprávnění. Právě tohle rozhodnutí způsobuje, že se níže popsané pravidlo kódování chová stejně jako Excel

Ochrana listu a zároveň ponechání řazení a filtrování otevřených

Tady je běžný případ od začátku do konce: ochraňte hotový report tak, aby šlo změnit rozvržení, ale čtenář ho mohl třídit a filtrovat. Všimněte si, že Protect a možnosti jsou nezávislé. Protect přepne list do chráněného stavu a uloží volitelný hash hesla, ale na sadu možností nesahá. AllowOption upravujete zvlášť a přepínače se projeví, jakmile je list chráněný a uložený

var
  wb: TXLSXWorkbook;
  sh: TXLSXWorksheet;
begin
  wb := TXLSXWorkbook.Create;
  try
    sh := wb.Sheets.Add('Protected');
    sh.Cells[1, 1].Value := 'Region'; sh.Cells[1, 2].Value := 'Units';
    sh.Cells[2, 1].Value := 'North';  sh.Cells[2, 2].Value := 120;
    sh.Cells[3, 1].Value := 'South';  sh.Cells[3, 2].Value := 98;

    // Protect with a password. This only sets the protected state + hash;
    // the option set is left at its all-permitted default.
    sh.Protect('HotXLS-2026');

    // Narrow: keep sort + AutoFilter, forbid reshaping and reformatting.
    sh.AllowOption[xlsxSpoSort]          := True;
    sh.AllowOption[xlsxSpoAutoFilter]    := True;
    sh.AllowOption[xlsxSpoFormatCells]   := False;
    sh.AllowOption[xlsxSpoFormatColumns] := False;
    sh.AllowOption[xlsxSpoFormatRows]    := False;
    sh.AllowOption[xlsxSpoInsertRows]    := False;
    sh.AllowOption[xlsxSpoDeleteRows]    := False;

    if wb.SaveAs('protection.xlsx') <> 1 then
      Writeln('SaveAs failed');
  finally
    wb.Free;
  end;
end;

Z té ukázky si odnesete dvě věci. Řádky Sort a AutoFilter se zapisují výslovně, i když jsou obě výchozí na True; to je dokumentace pro dalšího správce, ne funkční požadavek. A protože výchozí stav je permisivní, jediné řádky, které mění výstupní soubor, jsou ty, kde se možnost nastaví na False. To není náhoda tohoto API, ale projev wire formátu OOXML, a právě k němu míří další část

Pravidlo kódování: chybí znamená povoleno, attr=0 znamená zakázáno

Tohle je jediný nelogický detail v celé funkci a místo, kde ručně psaný <sheetProtection> obvykle selže. V OOXML je každý atribut pro jednotlivou akci zakázat příznak a jeho absence znamená povolení. Chybějící atribut znamená, že je akce povolená. Atribut zapsaný jako "0" znamená, že je akce při chráněném listu zakázaná. V dobře vytvořeném souboru neexistuje formatCells="1" pro význam "formátování je povolené"; atribut prostě vynecháte. (Výchozí hodnota pro chybějící atribut je boolovský standard OOXML true a tyto atributy jsou pojmenované tak, že true znamená, že odpovídající úprava je povolená.)

Zapisovač HotXLS to přesně kopíruje. Zapíše sheet="1" pro zapnutí ochrany a pak projde sadu možností a zapíše attr="0" jen pro ty možnosti, které nastavíte na False. Povolené akce do výstupu nepřidávají nic. Sešit z předchozí části se tedy serializuje zhruba takto, s uloženými jen zakázanými akcemi a hashem hesla:

// Conceptual output for the snippet above (attributes elided for brevity):
// <sheetProtection sheet="1"
//   formatCells="0" formatColumns="0" formatRows="0"
//   insertRows="0" deleteRows="0"
//   password="...4-hex..."/>
// Note what is NOT there: no sort, no autoFilter, no selectLockedCells.
// Their absence is exactly what tells Excel those actions stay allowed.

Pokud jste přišli ze starého pevně daného řetězce a čekali jste, že uvidíte všechny atributy výslovně vypsané, působí to úsporně, skoro špatně. Je to správně. Soubor, který by uvedl sort="1" a autoFilter="1" by pro kompatibilní čtečku znamenal totéž, ale samotný Excel zapisuje minimální podobu jen s položkami zakazujícími, a držet se jí znamená menší diffy a nudné round-tripy. Atributy objects a scenarios se řídí stejným pravidlem: ve výchozím stavu jsou povolené, takže se objeví jen jako "0" ve chvíli, kdy je zakážete, což je obráceně oproti starému objects="1" scenarios="1" které se zapisovalo bez podmínky

Čtení ochrany zpět: věrnost round-tripu

Model oprávnění, který umíte zapsat, ale ne přečíst, je jednosměrná ulice a obvyklý příznak je cyklus načíst-upravit-uložit, který potichu rozšíří oprávnění. HotXLS to řeší. Když ParseWorksheetXml narazí na prvek <sheetProtection> , nastaví list jako chráněný, zachytí hash hesla, pokud existuje, a potom dekóduje každý atribut po jednotlivých akcích zpět do AllowOption stejnou konvencí opačným směrem: přítomný atribut rovný "0" akci zakazuje, chybějící atribut ponechá možnost v jejím výchozím stavu povolení

var
  wb: TXLSXWorkbook;
  sh: TXLSXWorksheet;
begin
  wb := TXLSXWorkbook.Create;
  try
    wb.LoadFromFile('protection.xlsx');
    sh := wb.Sheets[1];                  // XLSX sheets are 1-based
    if sh.IsProtected then
    begin
      Writeln('Protected; password hash present: ',
        sh.SheetProtectHash <> '');
      Writeln('Sort allowed:       ', sh.AllowOption[xlsxSpoSort]);
      Writeln('AutoFilter allowed: ', sh.AllowOption[xlsxSpoAutoFilter]);
      Writeln('FormatCells allowed:', sh.AllowOption[xlsxSpoFormatCells]);
    end;
  finally
    wb.Free;
  end;
end;

Načtěte soubor, který zapisovač vyrobil, a dostanete Sort a AutoFilter zpět jako True, FormatCells jako False - přesně tu sadu, kterou jste uložili. Právě o to jde: upravíte jednu buňku na chráněném, částečně povoleném listu a znovu uložíte, a čtrnáct oprávnění, kterých jste se nedotkli, zůstanou zachovaná místo toho, aby se zhroutila zpět do starého režimu vše nebo nic

Praktické poznámky a omezení

Předtím, než to zapojíte do reportovacího procesu, stojí za to vědět pár věcí:

  • Heslo je záměrně slabé. Ochrana listu XLSX ukládá historický 16bitový hash (ten samý, který Excel používá desítky let) kvůli kompatibilitě. Brání náhodným úpravám, ale neodolá útočníkovi. Nepovažujte ho za správce tajemství. Pro skutečnou ochranu zašifrujte celý sešit.
  • Nastavit možnosti před ochranou je v pořádku. AllowOption lze přiřadit bez ohledu na to, zda je list právě chráněný; přepínače jen popisují, co ochrana dovolí, až bude Protect aktivní. UnProtect zruší chráněný stav i hash, ale sadu možností ponechá pro příště.
  • Semantika zamknutých buněk dál platí. Ochrana blokuje jen úpravy buněk, u kterých je nastaven atribut Locked (výchozí stav sešitu). Nechat vstupní oblast editovatelnou je úkol stylu buňky, ne volby ochrany; obě vrstvy se kombinují stejně jako v Excelu.
  • Toto je engine XLSX. Model možností odpovídá starším vlastnostem Allow* enginu XLS, ale názvy enumu a property tady (xlsxSpo*, AllowOption) patří do TXLSXWorksheet v lxHandleX. Pokud na stejných listech řídíte i rozvržení tisku, průvodce ochrana a nastavení stránky ukazuje, jak tyto volby stojí vedle tiskových oblastí a záhlaví, a ověřování dat, AutoFilter a tabulky se přirozeně páruje s ponecháním xlsxSpoAutoFilter otevřeného na uzamčeném reportu.

Model jemně odstupňované ochrany a zbytek engine pro čtení a zápis XLSX jsou součástí HotXLS Component pro Delphi a C++Builder; stránka produktu obsahuje kompletní API listu včetně úplné reference možností ochrany