Artykuł techniczny

HotXLS: ochrona arkusza, ustawienia strony i wydruk

Trzy grupy ustawień arkusza nie mają nic wspólnego z wartościami komórek, a wszystko z tym, jak plik zachowuje się po opuszczeniu twojego kodu. Ochrona arkusza decyduje, które komórki użytkownik może edytować po przekazaniu mu skoroszytu. Ustawienia strony ustalają orientację, rozmiar papieru i marginesy. Ustawienia wydruku (powtarzane wiersze tytułowe, skalowanie i ręczne podziały stron) kontrolują to, jak siatka o dowolnej długości ląduje na papierze. Żadna z tych trzech nie pokazuje się, gdy oglądasz dane w przeglądarce plików, a wszystkie trzy psują się po cichu w terenie, gdy są błędne. HotXLS, natywna biblioteka arkuszowa dla Delphi i C++Buildera, wystawia pełną powierzchnię dla .xls i .xlsx, co znaczy, że odtwarza też każdą sprzeczną z intuicją regułę Excela wpieczoną w tę powierzchnię

Pierwsza z tych reguł podkłada nogę prawie każdemu, kto po raz pierwszy chroni wygenerowany arkusz. Wołasz Protect i nagle nikt nie może pisać w żadnej komórce, łącznie z kolumnami wejściowymi, wokół których zbudowałeś skoroszyt. Nic w twoim kodzie nie tknęło tych kolumn i właśnie dlatego tak się dzieje

Każda komórka rodzi się zablokowana

ECMA-376 definiuje locked jako część rekordu formatowania komórki, a nie jako właściwość samej ochrony, i domyślnie ma ono wartość prawdy. Ochrona arkusza jest jedynie przełącznikiem, który czyni tę flagę egzekwowalną. Więc cała siatka niesie flagę blokady od chwili swojego istnienia, uśpioną, a wywołanie Protect aktywuje je wszystkie naraz. Lekarstwem jest świadome ustawienie kolejności: zbuduj układ, jawnie odblokuj zakresy, które użytkownicy muszą edytować, i chroń na końcu

Diagram kolejności ochrony w HotXLS w Delphi, gdzie każda komórka rodzi się z locked równym prawdzie, zakresy wejściowe są najpierw odblokowywane przez SetLocked, a wywołane na końcu Sheet.Protect zostawia odblokowane komórki edytowalnymi
Komórki przychodzą domyślnie zablokowane, więc najpierw odblokuj zakresy wejściowe, a Protect wołaj na końcu, żeby zostały edytowalne
Book := TXLSXWorkbook.Create;
try
  Sheet := Book.Sheets.Add('Timesheet');
  // ... tutaj zapisany wiersz nagłówka, kolumna nazwisk i formuły stawek ...
  Sheet.Range['B2:B50'].SetLocked(False);         // tutaj pracownicy wpisują godziny
  Sheet.Range['F2:F50'].SetFormulaHidden(True);   // trzymaj matematykę stawek w tajemnicy
  Sheet.Protect('review-2026');                   // teraz flagi blokady zaczynają gryźć
  Book.SaveAs('timesheet.xlsx');
finally
  Book.Free;
end;

SetFormulaHidden robi coś osobnego i łatwego do przeoczenia: przy aktywnej ochronie komórka nadal pokazuje swoją policzoną wartość, ale pasek formuły nie pokazuje nic. To ma znaczenie, gdy formuła zawiera stawki rozliczeniowe, marże albo wagi punktacji, których wolałbyś nie wręczać każdemu odbiorcy klikającemu w sumę. Na fasadzie XLS ten sam zamiar wyraża się dla każdego zakresu przez IXLSRange.Locked i FormulaHidden. Tamtejszy arkusz niesie też piętnaście flag Allow* (AllowSort, AllowAutoFilter, AllowFormatCells i reszta), więc chroniony arkusz nadal da się sortować i filtrować, zamiast zamarzać w zapieczętowany eksponat

Co naprawdę chroni hasło ochrony

Oba formaty przechowują hasło ochrony arkusza i skoroszytu jako starszy skrót o czterech cyfrach szesnastkowych. Szesnaście bitów oznacza, że z dowolnym hasłem koliduje niezliczona liczba ciągów, a narzędzia do zdejmowania ochrony są o jedno wyszukanie stąd. Traktuj ochronę jak pas bezpieczeństwa przed przypadkową edycją, a nie jak kontrolę dostępu. To właściwe narzędzie, by powstrzymać recenzentów przed nadpisaniem kolumny formuł, i niewłaściwe do czegokolwiek, w czym pada słowo poufne

Piętro wyżej ProtectWorkbook na fasadzie XLSX blokuje strukturę skoroszytu, co uniemożliwia dodawanie, przemianowywanie, usuwanie i zmianę kolejności arkuszy. Ustaw je zawsze, gdy sama lista arkuszy jest kontraktem z parserem niżej w łańcuchu, który indeksuje arkusze po nazwie albo pozycji. Przemianowany arkusz psuje import po drugiej stronie równie pewnie jak usunięta kolumna. Fasada XLS odzwierciedla to warstwowanie przez TXLSWorkbook.Protect na poziomie skoroszytu i wywołania Protect na poszczególnych arkuszach, plus właściwość isProtected dla kodu, który musi obejrzeć odziedziczony plik, zanim cokolwiek w nim zmieni

Kiedy wymaganiem jest prawdziwa poufność, mechanizm zmienia się całkowicie. SaveAsEncrypted produkuje pakiet szyfrowany AES w schemacie Standard Encryption z ECMA-376, omówionym dogłębnie w przewodniku po wyjściu XLSX chronionym AES, a starsza fasada XLS zapisuje i czyta pliki .xls szyfrowane RC4 przez EncryptionPassword i przeciążenie Open z hasłem. Różnica nie jest akademicka. Chroniony arkusz podróżuje jawnym tekstem, więc dowolne narzędzie zip odczyta jego wartości komórek, podczas gdy zaszyfrowany pakiet jest nieczytelny bez hasła. Linia audytu mówiąca "plik płacowy musi być chroniony" prawie zawsze znaczy szyfrowanie, jakiegokolwiek słownictwa użyje

Diagram zestawiający ochronę arkusza w HotXLS, która przechowuje starszy skrót szesnastobitowy i zostawia wartości komórek jawnym tekstem czytelnym dla dowolnego narzędzia zip, z wyjściem AES z SaveAsEncrypted, które pozostaje nieczytelne bez hasła
Ochrona arkusza to pas bezpieczeństwa przed przypadkową edycją, podczas gdy jawne wartości pozostają czytelne, a treść ukrywa dopiero szyfrowanie AES

Ustawienia strony są częścią kontraktu dokumentu

Zachowanie wydruku jest niewidoczne na ekranie i dlatego tak często wysyłane jest zepsute. W chwili, gdy klient drukuje skoroszyt albo eksportuje go do PDF dla audytora, marginesy, skalowanie i powtarzane tytuły zamieniają się w wymagania funkcjonalne, których nikt nie przetestował. Na fasadzie XLSX te ustawienia wiszą wprost na arkuszu:

Sheet.PageLandscape := True;
Sheet.PaperSize := xlsxPaperA4;
Sheet.SetPageMargins(0.5, 0.5, 0.75, 0.75, 0.3, 0.3);
Sheet.CenterHeader := 'Monthly Timesheet';
Sheet.RightFooter := 'Page &P of &N';
Sheet.PrintArea := '$A$1:$F$60';     // goła referencja: bez nazwy arkusza
Sheet.PrintTitleRows := '$1:$1';     // wiersz nagłówka powtarza się na każdej stronie
Sheet.FitToWidth := 1;
Sheet.FitToHeight := 0;              // rośnie w dół wraz z danymi
Sheet.PrintGridlines := False;

Dwie z tych linii kryją pułapki. Ciągi nagłówka i stopki używają kodów formatowania Excela: &P dla bieżącej strony, &N dla łącznej liczby, wraz z &L, &C i &R do jawnego zaadresowania trzech sekcji. Drugą pułapką jest PrintArea, które celowo bierze gołą referencję komórkową. HotXLS przechowuje ją niekwalifikowaną i dokleja nazwę arkusza przy zapisie pliku, więc podanie 'Timesheet!$A$1:$F$60' samodzielnie produkuje podwójnie kwalifikowaną, zniekształconą referencję. Ta sama ostrożność obowiązuje warstwę niżej: obszary wydruku i tytuły wydruku są utrwalane jako wbudowane nazwy zdefiniowane _xlnm.Print_Area i _xlnm.Print_Titles, więc nigdy nie dodawaj wpisów _xlnm.* ręcznie przez DefinedNames, bo oba mechanizmy pobiją się o to samo miejsce

Skalowanie, które przeżywa produkcyjne wolumeny danych

Kombinacja FitToWidth := 1 z FitToHeight := 0 czyta się jako "zawsze zmieść kolumny w szerokości jednej strony, a potem weź w dół tyle stron, ile trzeba danym" i jest poprawnym ustawieniem domyślnym dla każdego raportu o zmiennej liczbie wierszy. Pułapką jest strojenie stałego procentu albo pary dopasowań do strony na trzydziestowierszowym pliku testowym: podaj tym samym ustawieniom sześćset wierszy produkcyjnych, a wyjście albo wybuchnie dziesiątkami przyciętych stron, albo skurczy się poniżej czytelności. Skaluj szerokość, pozwól długości rosnąć i powtarzaj wiersz nagłówka przez PrintTitleRows, żeby siedemnasta strona nadal była czytelna sama z siebie

Diagram skalowania wydruku w HotXLS w Delphi z FitToWidth ustawionym na 1, żeby każda strona miała szerokość jednego arkusza, FitToHeight ustawionym na 0, żeby strony rosły w dół, PrintTitleRows powtarzającym pas nagłówka i podziałami stron odtworzonymi po ClearAllPageBreaks
FitToWidth 1 z FitToHeight 0 utrzymuje każdą stronę o szerokości jednego arkusza, a powtarzane wiersze tytułowe i odtworzone podziały zachowują czytelność

Ręczne podziały idą za tą samą dyscypliną odtwarzania co wszystko inne w generowanym skoroszycie. AddRowBreak(BeforeRow) zaczyna nową stronę przed granicą sekcji, ale gdy generator biegnie ponownie i wiersze się przesuwają, nieświeży podział ląduje w środku tabeli. Wołaj najpierw ClearAllPageBreaks, a potem dodaj podziały wyliczone z własnych liczników wierszy generatora, zamiast łatać stare pozycje. Na fasadzie XLS równoważne sterowanie mieszka na Sheet.PageSetup (orientacja, rozmiar papieru, marginesy, ciągi nagłówka i stopki, dopasowanie do stron), a RepeatRows i RepeatColumns pokrywają tytuły wydruku

Sprawdzanie wyniku, zanim zrobi to klient

Błędy ochrony i wydruku dzielą jedną właściwość: są trywialne do sprawdzenia ręcznie i prawie nigdy nie są sprawdzane. Otwórz wygenerowany plik w Excelu i poświęć mu dziewięćdziesiąt sekund. Wpisz coś w komórkę wejściową i potwierdź, że przyjmuje naciśnięcie klawisza; wpisz coś w komórkę zablokowaną i potwierdź, że pojawia się monit ochrony; sprawdź, czy ukryta formuła zostawia pasek formuły pusty. Potem uruchom podgląd wydruku na zbiorze danych wielkości produkcyjnej, a nie na trzydziestowierszowej próbce, i odczytaj liczbę stron, powtarzający się wiersz tytułowy i numerację w stopce. Podgląd to krok, który sam na siebie zarabia, bo geometria wydruku zależy od ustawień bez renderowania na ekranie, a poza fizyczną drukarką jest jedynym miejscem, gdzie błąd skalowania kiedykolwiek staje się widoczny

Jedno ostatnie ustawienie dopełnia przegląd. FreezePane(ACol, ARow) utrzymuje blok nagłówka w polu widzenia, gdy recenzent przewija. To zachowanie ekranowe, a nie wydrukowe, ale recenzent ocenia cały produkt naraz. A skoroszyt, który zaczyna życie jako układ utrzymywany przez projektanta, dostaje większość tego za darmo: przepływ generowania raportów z szablonów trzyma ustawienia strony w szablonie, gdzie człowiek nastroił je wobec prawdziwej drukarki, i zostawia kodowi wypełnienie danych oraz ponowne nałożenie ochrony, gdy układ się ustali

HotXLS to natywna biblioteka arkuszowa w Object Pascalu dla Delphi i C++Buildera; kompletna referencja API ochrony i ustawień strony jest na stronie produktu HotXLS Delphi Component