Вы передаете коллеге готовую книгу и просите его фильтровать ее, а не переписывать. Поэтому вы защищаете лист. В старых сборках HotXLS этот жест записывал в файл одну и ту же строку: <sheetProtection sheet="1" objects="1" scenarios="1"/> , жестко зашитую всегда. Лист блокировался, хеш пароля прикреплялся, а пользователь не мог делать вообще ничего, даже сортировку и фильтрацию, которые вы как раз хотели оставить доступными. В собственном диалоге Excel "Protect Sheet" для этого существует пятнадцать флажков, а движок не умел выразить ни один из них. Именно этот пробел и закрывает модель защиты из v2.91.0
HotXLS - это нативный VCL spreadsheet component для Delphi и C++Builder, который читает и пишет XLS и XLSX без установленного Excel. Эта статья посвящена стороне XLSX у защиты worksheet: новому enum TXLSXSheetProtectionOption , свойству AllowOption , которое переключает каждое разрешение, и одному правилу кодирования OOXML, на котором спотыкается почти каждый, кто пишет элемент <sheetProtection> вручную
Что на самом деле охраняет защита листа
Сначала граница применимости, потому что именно она определяет, насколько вообще этому можно доверять. Защита worksheet в формате OOXML spreadsheet, ECMA-376, является политикой взаимодействия, а не шифрованием. Она сообщает совместимому приложению, какие правки нужно отклонять, пока лист защищен. Значения ячеек по-прежнему лежат в xl/worksheets/sheetN.xml в открытом виде. Распакуйте .xlsx , и они окажутся прямо перед вами. Необязательный пароль хранится как короткий устаревший хеш, а не как ключ, который что-либо шифрует. Любой, кто переименует файл, откроет part и удалит строку <sheetProtection> , сможет читать и редактировать все
Поэтому защита отвечает на вопрос "не дай коллеге случайно испортить формулу", а не на вопрос "спрячь эти данные от мотивированного человека". Это разные задачи с разными инструментами. Если вам нужна конфиденциальность, вам нужна workbook-level encryption, описанная в AES-protected XLSX output , которая действительно шифрует пакет. Защита листа и шифрование книги хорошо сочетаются, но замком является только второе. Держите эту границу в уме, и все остальное на странице сводится к аккуратной plumbing
Пятнадцать опций и свойство AllowOption
Теперь каждый worksheet несет набор значений TXLSXSheetProtectionOption , описывающий, что пользователь все еще может делать, пока лист защищен. Элементы набора один к одному соответствуют атрибутам OOXML и флажкам в диалоге Excel:
xlsxSpoEditObjects,xlsxSpoEditScenarios- редактирование drawing object и сценариев what-ifxlsxSpoFormatCells,xlsxSpoFormatColumns,xlsxSpoFormatRows- изменение форматирования ячеек, столбцов и строкxlsxSpoInsertColumns,xlsxSpoInsertRows,xlsxSpoInsertHyperlinks- вставка столбцов, строк и ссылокxlsxSpoDeleteColumns,xlsxSpoDeleteRows- удаление столбцов и строкxlsxSpoSelectLockedCells,xlsxSpoSelectUnlockedCells- перемещение выделения на заблокированные или незаблокированные ячейкиxlsxSpoSort,xlsxSpoAutoFilter,xlsxSpoPivotTables- сортировка диапазонов, использование выпадающих списков AutoFilter и работа с PivotTable
Отдельные биты читаются и записываются через индексированное свойство AllowOption у TXLSXWorksheet. AllowOption[Opt] = True означает, что действие разрешено; установка в False запрещает его. Весь набор также доступен целиком через SheetProtectionOptions, TXLSXSheetProtectionOptions (обычный Pascal set of), так что вы можете сохранить его, восстановить или заменить полностью
Значение по умолчанию важно, и оно выбрано намеренно: свежесозданный worksheet стартует с разрешением всех опций. Конструктор заполняет SheetProtectionOptions полным диапазоном, [Low(TXLSXSheetProtectionOption)..High(TXLSXSheetProtectionOption)]. Дальше вы сужаете этот набор, исключая действия, которые хотите запретить, а не собираете систему разрешений с нуля. Именно это решение и делает правило кодирования, описанное ниже, согласованным с поведением Excel
Защищаем лист, но оставляем сортировку и фильтрацию открытыми
Вот типичный сценарий целиком: защитить готовый отчет так, чтобы макет нельзя было перекроить, но при этом позволить читателю сортировать и фильтровать его. Обратите внимание, что Protect и набор опций независимы. Protect переводит лист в защищенное состояние и сохраняет необязательный хеш пароля. Сам набор опций при этом не меняется. Вы отдельно настраиваете AllowOption, и переключатели начинают действовать после защиты и сохранения листа
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;
// Защитите паролем. Это только задаёт защищённое состояние + хеш;
// набор параметров остаётся на своём разрешающем всё значении по умолчанию.
sh.Protect('HotXLS-2026');
// Сузьте: сохранить сортировку + AutoFilter, запретить изменение формы и переформатирование.
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;
Из этого фрагмента нужно считать две вещи. Строки Sort и AutoFilter записаны явно, хотя обе и так по умолчанию равны True. Это документация для следующего сопровождающего, а не функциональная необходимость. А поскольку значения по умолчанию разрешительные, единственными строками, которые реально меняют выходной файл, остаются те, где опция переводится в False. Это не случайность данного API, а прямое проявление формата OOXML, о чем и говорится в следующем разделе
Правило кодирования: отсутствие означает разрешено, attr=0 означает запрещено
Это единственный по-настоящему контринтуитивный факт во всей функции, и именно то место, где вручную написанный <sheetProtection> элемент обычно ломается. В OOXML каждый атрибут для отдельного действия является запретительным флагом, а его отсутствие - разрешение. Отсутствующий атрибут означает, что действие разрешено. Атрибут, записанный как "0", означает, что действие запрещено, пока лист защищен. В корректном файле нет formatCells="1" в смысле "formatting allowed" - вы просто не пишете этот атрибут вообще. (Значение по умолчанию для отсутствующего атрибута равно булевому default OOXML true, а сами атрибуты названы так, что true означает разрешение соответствующего редактирования.)
Writer в HotXLS отражает это правило буквально. Он выводит sheet="1", чтобы включить защиту, затем проходит по набору опций и записывает attr="0" только для тех опций, которые вы установили в False. Разрешенные действия вообще ничего не добавляют в output. Поэтому workbook из предыдущего раздела сериализуется примерно так, неся лишь запрещенные действия плюс хеш пароля:
// Концептуальный вывод для фрагмента выше (атрибуты опущены для краткости):
// <sheetProtection sheet="1"
// formatCells="0" formatColumns="0" formatRows="0"
// insertRows="0" deleteRows="0"
// password="...4-hex..."/>
// Обратите внимание, чего здесь НЕТ: ни sort, ни autoFilter, ни selectLockedCells.
// Их отсутствие ровно то, что сообщает Excel, что эти действия остаются разрешёнными.
Если вы пришли от старой жестко зашитой строки и ожидали увидеть в явном виде каждый атрибут, такой результат покажется чересчур разреженным и почти неправильным. На самом деле он корректен. Файл, который перечислил бы sort="1" и autoFilter="1", значил бы для совместимого reader то же самое, но сам Excel записывает минимальную форму только с запретами, а совпадение с этим поведением делает diff небольшими, а round-trip скучными и надежными. Атрибуты objects и scenarios следуют тому же правилу: они по умолчанию разрешены, поэтому появляются только как "0", когда вы их запрещаете. Это обратная логика по сравнению со старой строкой objects="1" scenarios="1", которая раньше выводилась безусловно
Чтение защиты обратно: точность round-trip
Модель разрешений, которую можно записать, но нельзя прочитать обратно, - это билет в одну сторону. Типичный симптом здесь состоит в том, что цикл load-edit-save молча расширяет разрешения. HotXLS закрывает и эту дыру. Когда ParseWorksheetXml встречает элемент <sheetProtection>, он помечает лист как защищенный, захватывает хеш пароля, если тот присутствует, а затем декодирует каждый атрибут отдельного действия обратно в AllowOption, используя то же соглашение, но в обратную сторону: присутствующий атрибут со значением "0" запрещает действие, а отсутствие атрибута оставляет опцию в разрешенном состоянии по умолчанию
var
wb: TXLSXWorkbook;
sh: TXLSXWorksheet;
begin
wb := TXLSXWorkbook.Create;
try
wb.Open('protection.xlsx');
sh := wb.Sheets[1]; // листы XLSX отсчитываются от 1
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;
Загрузите файл, который записал writer, и вы получите Sort и AutoFilter обратно как True, FormatCells как False - тот самый набор, который вы сохранили. В этом и состоит вся ценность симметрии: вы меняете одну ячейку на защищенном листе с частично разрешенными действиями, пересохраняете файл, и четырнадцать разрешений, которых вы не касались, не схлопываются обратно к старому all-or-nothing поведению
Практические замечания и ограничения
Несколько вещей полезно знать, прежде чем включать это в pipeline отчетов:
- Пароль по замыслу слабый. Защита worksheet в XLSX хранит 16-битный устаревший хеш, тот самый, который Excel использует десятилетиями, - сохранен здесь ради совместимости. Он удерживает от случайных правок, но не сопротивляется атакующему. Не считайте его хранителем секретов. Для настоящей защиты шифруйте workbook
- Настраивать опции до включения защиты нормально. Значение
AllowOptionможно назначать вне зависимости от того, защищен лист сейчас или нет; эти переключатели всего лишь описывают, что будет разрешено после того, какProtectвступит в силу.UnProtectочищает флаг защищенности и хеш, но оставляет набор опций на месте для следующего раза - Семантика locked cell по-прежнему действует. Защита блокирует только изменения в тех ячейках, у которых атрибут
Lockedустановлен (это поведение workbook по умолчанию). Оставить область ввода редактируемой - задача стиля ячейки, а не опции защиты; эти два слоя сочетаются точно так же, как в Excel - Это относится к движку XLSX. Модель опций повторяет более старые свойства
Allow*у движка XLS, но enum и имена свойств здесь (xlsxSpo*,AllowOption) принадлежатTXLSXWorksheetвlxHandleX. Если вы теми же листами управляете и с точки зрения печати, то разбор protection и page setup показывает, как эти настройки живут рядом с print area и header, а data validation, AutoFilter and tables естественно сочетается с тем, чтобы оставлятьxlsxSpoAutoFilterоткрытым на заблокированном отчете
Детализированная модель защиты и остальная часть движка чтения/записи XLSX поставляются в составе HotXLS Delphi Component для Delphi и C++Builder; страница продукта содержит полный API worksheet, включая полный справочник по опциям защиты