Техническая статья

Защита листа XLSX в Delphi: 15 параметров Allow

Вы передаете коллеге готовую книгу и просите его фильтровать ее, а не переписывать. Поэтому вы защищаете лист. В старых сборках 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

Диаграмма контраста: защита листа XLSX в Delphi в HotXLS как политика взаимодействия против шифрования книги AES как единственного настоящего замка
Защита листа отвергает правки, пока значения остаются открытым текстом; только шифрование книги зашифровывает пакет

Пятнадцать опций и свойство AllowOption

Теперь каждый worksheet несет набор значений TXLSXSheetProtectionOption , описывающий, что пользователь все еще может делать, пока лист защищен. Элементы набора один к одному соответствуют атрибутам OOXML и флажкам в диалоге Excel:

  • xlsxSpoEditObjects , xlsxSpoEditScenarios - редактирование drawing object и сценариев what-if
  • xlsxSpoFormatCells , 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", которая раньше выводилась безусловно

Диаграмма пятнадцати значений TXLSXSheetProtectionOption в HotXLS, сгруппированных в шесть семейств и переключаемых через индексируемое свойство AllowOption и набор SheetProtectionOptions в Delphi
Пятнадцать опций группируются в шесть семейств, каждое переключается бит за битом или меняется оптом как Pascal-множество

Чтение защиты обратно: точность 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 поведению

Диаграмма правила кодирования sheetProtection XLSX в HotXLS в Delphi: опущенный атрибут разрешает действие, а attr=0 запрещает; контраст старой жёстко зашитой строки с минимальным выводом писателя «только запреты»
Разрешённое действие не вносит никакого атрибута вовсе; только запрещённые действия появляются как attr=0

Практические замечания и ограничения

Несколько вещей полезно знать, прежде чем включать это в 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, включая полный справочник по опциям защиты