Przekazujesz gotowy skoroszyt współpracownikowi i prosisz, aby go filtrował, a nie przepisywał. Więc włączasz ochronę arkusza. W starszych buildach HotXLS ten gest zapisywał do pliku tylko jedną rzecz: <sheetProtection sheet="1" objects="1" scenarios="1"/>, na sztywno, za każdym razem. Arkusz był blokowany, hash hasła dołączany, a użytkownik nie mógł zrobić niczego, nawet sortowania i filtrowania, które faktycznie chciałeś zostawić otwarte. Własne okno dialogowe Excela "Protect Sheet" ma z tego powodu piętnaście pól wyboru, a silnik nie potrafił wyrazić żadnego z nich. Tę lukę domyka model ochrony z wersji v2.91.0
HotXLS to natywny komponent VCL arkuszy kalkulacyjnych dla Delphi i C++Builder, który odczytuje i zapisuje pliki XLS i XLSX bez zainstalowanego Excela. Ten artykuł dotyczy strony XLSX ochrony worksheet: nowego TXLSXSheetProtectionOption enum, właściwości AllowOption, która przełącza każde uprawnienie, oraz jednej reguły kodowania OOXML, na której potyka się każdy, kto ręcznie zapisuje element <sheetProtection>
Co naprawdę chroni ochrona worksheet
Najpierw granica, bo to ona decyduje, jak bardzo w ogóle należy temu ufać. Ochrona worksheet w formacie arkuszy OOXML (ECMA-376) jest polityką interakcji, a nie szyfrowaniem. Mówi zgodnej aplikacji, których edycji ma odmawiać, gdy arkusz jest chroniony. Wartości komórek nadal leżą w xl/worksheets/sheetN.xml w postaci zwykłego tekstu; rozpakuj .xlsx i są tam od razu widoczne. Opcjonalne hasło jest przechowywane jako krótki historyczny hash, a nie klucz szyfrujący cokolwiek. Każdy, kto zmieni nazwę pliku, otworzy część i usunie linię <sheetProtection>, może wszystko odczytać i edytować
Ochrona odpowiada więc na pytanie "powstrzymaj współpracownika przed przypadkowym nadpisaniem formuły", a nie "ukryj te dane przed kimś zdeterminowanym". To różne problemy wymagające różnych narzędzi. Jeśli potrzebujesz poufności, chcesz szyfrowania na poziomie workbook opisanego w AES-protected XLSX output, które rzeczywiście szyfruje pakiet. Ochrona arkusza i szyfrowanie workbook dobrze się łączą, ale tylko to drugie jest prawdziwą blokadą. Jeśli tę granicę masz jasno ustawioną, reszta tej strony to już tylko hydraulika
Piętnaście opcji i właściwość AllowOption
Każdy worksheet przechowuje teraz zestaw wartości TXLSXSheetProtectionOption opisujących, co użytkownik nadal może robić, gdy arkusz jest chroniony. Poszczególne elementy mapują się jeden do jednego na atrybuty OOXML i pola wyboru w oknie dialogowym Excela:
xlsxSpoEditObjects,xlsxSpoEditScenarios— edycja obiektów drawing i scenariuszy what-ifxlsxSpoFormatCells,xlsxSpoFormatColumns,xlsxSpoFormatRows— ponowne formatowanie komórek, kolumn i wierszyxlsxSpoInsertColumns,xlsxSpoInsertRows,xlsxSpoInsertHyperlinks— wstawianie kolumn, wierszy i linkówxlsxSpoDeleteColumns,xlsxSpoDeleteRows— usuwanie kolumn i wierszyxlsxSpoSelectLockedCells,xlsxSpoSelectUnlockedCells— przenoszenie zaznaczenia na zablokowane lub odblokowane komórkixlsxSpoSort,xlsxSpoAutoFilter,xlsxSpoPivotTables— sortowanie zakresów, używanie list rozwijanych AutoFilter i praca z PivotTables
Poszczególne bity odczytujesz i zapisujesz przez indeksowaną właściwość AllowOption w TXLSXWorksheet. AllowOption[Opt] = True oznacza, że akcja jest dozwolona; ustawienie go na False ją zabrania. Do całego zestawu można też dostać się jednocześnie przez SheetProtectionOptions, czyli TXLSXSheetProtectionOptions (zwykły pascalowy set of), więc możesz go zapisać, odtworzyć albo podmienić w całości
Domyślne ustawienie ma znaczenie i jest celowe: świeżo utworzony worksheet startuje z dozwolonymi wszystkimi opcjami. Konstruktor inicjalizuje SheetProtectionOptions pełnym zakresem, [Low(TXLSXSheetProtectionOption)..High(TXLSXSheetProtectionOption)]. Od tego miejsca zawężasz zestaw, wykluczając akcje, których chcesz zabronić, zamiast budować uprawnienia od zera. To właśnie ten wybór sprawia, że opisana niżej reguła kodowania writer jest zgodna z zachowaniem Excela
Ochrona arkusza z pozostawieniem sortowania i filtrowania otwartego
Oto typowy przypadek od początku do końca: chroń gotowy raport tak, aby nie dało się przebudować układu, ale pozwól czytelnikowi go sortować i filtrować. Zauważ, że Protect i same opcje są od siebie niezależne. Protect przełącza arkusz w stan chroniony i zapisuje opcjonalny hash hasła; nie dotyka zestawu opcji. AllowOption ustawiasz osobno, a przełączniki zaczynają działać po ochronie i zapisaniu arkusza
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 tego fragmentu warto odczytać dwie rzeczy. Linie Sort oraz AutoFilter są zapisane jawnie, mimo że obie domyślnie mają wartość True; to dokumentacja dla kolejnego maintainer, a nie wymóg funkcjonalny. A ponieważ domyślne ustawienia są liberalne, jedyne linie zmieniające plik wyjściowy to te, które ustawiają opcję na False. To nie przypadek tego API, tylko prześwitujący format wire OOXML, o którym jest następna sekcja
Reguła kodowania: omit oznacza allow, attr=0 oznacza forbid
To jedyny nieintuicyjny fakt w całej tej funkcji i miejsce, w którym ręcznie napisany <sheetProtection> zwykle idzie źle. W OOXML każdy atrybut dla pojedynczej akcji jest flagą forbid, a jego brak oznacza pozwolenie. Brak atrybutu oznacza, że akcja jest dozwolona. Atrybut zapisany jako "0" oznacza, że akcja jest zabroniona, gdy arkusz jest chroniony. W poprawnym pliku nie ma formatCells="1" znaczącego "formatowanie jest dozwolone"; po prostu pomijasz atrybut. (Wartość domyślna dla nieobecnego atrybutu to domyślne OOXML boolean true, a nazwy tych atrybutów są dobrane tak, że "true" oznacza dozwolenie odpowiedniej edycji.)
Writer w HotXLS odwzorowuje to dokładnie. Zapisuje sheet="1", aby włączyć ochronę, po czym przechodzi po zestawie opcji i zapisuje attr="0" tylko dla opcji ustawionych na False. Dozwolone akcje nie wnoszą nic do wyjścia. Dlatego skoroszyt z poprzedniej sekcji serializuje się mniej więcej tak, przenosząc tylko zabronione akcje oraz hash hasła:
// 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.
Jeśli przychodzisz ze starego hard-coded string i spodziewałeś się zobaczyć jawnie wypisany każdy atrybut, to wygląda skąpo, wręcz podejrzanie. To poprawne. Plik, który zawierałby sort="1" oraz autoFilter="1", znaczyłby to samo dla zgodnego czytnika, ale sam Excel zapisuje minimalną postać typu forbid-only, a dopasowanie się do niej utrzymuje małe diffy i nudne round-tripy. Atrybuty objects oraz scenarios stosują identyczną regułę: domyślnie są dozwolone, więc pojawiają się tylko jako "0", kiedy ich zabronisz, co jest odwrotnością starego objects="1" scenarios="1" emitowanego bezwarunkowo
Odczyt ochrony z powrotem: wierność round-trip
Model uprawnień, który możesz zapisać, ale nie możesz go odczytać, jest drzwiami w jedną stronę, a zwykłym objawem jest cykl load-edit-save, który po cichu poszerza uprawnienia. HotXLS to domyka. Gdy ParseWorksheetXml trafia na element <sheetProtection>, ustawia arkusz jako chroniony, przechwytuje hash hasła, jeśli występuje, a następnie dekoduje każdy atrybut przypisany do akcji z powrotem do AllowOption przy użyciu tej samej konwencji w drugą stronę: atrybut obecny i równy "0" zabrania akcji; brak atrybutu pozostawia opcję przy domyślnie dozwolonej wartości
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;
Wczytaj plik zapisany przez writer, a dostaniesz Sort oraz AutoFilter z powrotem jako True, a FormatCells jako False — dokładnie ten zestaw, który zapisałeś. Ta symetria jest tu całym celem: edytujesz jedną komórkę w chronionym arkuszu z częściowo otwartymi uprawnieniami i zapisujesz ponownie, a czternaście pozostałych uprawnień, których nie dotknąłeś, przetrwa zamiast zapaść się z powrotem do starego domyślnego modelu wszystko-albo-nic
Uwagi praktyczne i ograniczenia
Kilka rzeczy, które warto wiedzieć, zanim włączysz to do pipeline raportowego:
- Hasło jest z założenia słabe. Ochrona worksheet w XLSX przechowuje 16-bitowy historyczny hash, ten sam, którego Excel używa od dekad, zachowany tu ze względów interoperacyjności. Powstrzymuje przypadkowe edycje, ale nie broni przed atakującym. Nie traktuj tego jak mechanizmu przechowywania tajemnicy. Jeśli potrzebujesz realnej ochrony, zaszyfruj workbook
- Ustawianie opcji przed ochroną jest w porządku.
AllowOptionmożna przypisywać niezależnie od tego, czy arkusz jest obecnie chroniony; przełączniki po prostu opisują, na co ochrona pozwoli, gdy tylkoProtectzacznie obowiązywać.UnProtectczyści stan ochrony i hash, ale zostawia zestaw opcji na następny raz - Semantyka locked-cell nadal obowiązuje. Ochrona blokuje tylko edycję komórek, których atrybut
Lockedjest ustawiony, co jest domyślnym zachowaniem workbook. Pozostawienie regionu wejściowego jako edytowalnego to zadanie stylu komórki, a nie opcji ochrony; obie warstwy łączą się tak samo jak w Excelu - To jest silnik XLSX. Model opcji odzwierciedla starsze właściwości
Allow*w silniku XLS, ale enum i nazwy właściwości tutaj (xlsxSpo*,AllowOption) należą doTXLSXWorksheetwlxHandleX. Jeśli na tych samych arkuszach sterujesz też układem wydruku, omówienie ochrony i page setup pokazuje, jak te ustawienia współistnieją z obszarami wydruku i nagłówkami, a data validation, AutoFilter and tables naturalnie łączy się z pozostawieniemxlsxSpoAutoFilterotwartego w zablokowanym raporcie
Model ochrony o drobnej granulacji i reszta silnika odczytu/zapisu XLSX są dostarczane w HotXLS Component dla Delphi i C++Builder; strona produktu zawiera pełne API worksheet, w tym kompletny opis opcji ochrony