Технічна стаття

Захист аркуша XLSX у Delphi: 15 параметрів AllowOption

Ви передаєте готову книгу колезі й просите його відфільтрувати її, а не переписувати. Тож ви захищаєте аркуш. У старіших збірках HotXLS цей жест записував у файл лише одне: <sheetProtection sheet="1" objects="1" scenarios="1"/>, жорстко закодоване, кожного разу. Аркуш блокувався, хеш пароля додавався, і користувач не міг зробити взагалі нічого, навіть сортування й фільтрацію, які ви хотіли залишити відкритими. У діалозі Excel "Protect Sheet" є п'ятнадцять прапорців саме з цієї причини, а рушій не міг представити жоден із них. Саме цю прогалину закриває модель захисту v2.91.0

HotXLS - це нативний VCL-компонент електронних таблиць для Delphi та C++Builder, який читає і записує XLS та XLSX без встановленого Excel. Ця стаття присвячена частині захисту аркуша у XLSX: новому TXLSXSheetProtectionOption enum, а AllowOption властивості, яка вмикає або вимикає кожен дозвіл, і єдиному правилу кодування OOXML, на якому спотикається кожен, хто пише <sheetProtection> елемент вручну

Що саме захищає захист аркуша

Спершу межа, бо від неї залежить, наскільки всьому цьому можна довіряти. Захист аркуша у форматі електронних таблиць OOXML (ECMA-376) є політикою взаємодії, а не шифруванням. Він підказує сумісній програмі, які редагування відхиляти, поки аркуш захищений. Значення клітинок і далі лежать у xl/worksheets/sheetN.xml у відкритому вигляді; розпакуйте .xlsx і вони прямо там. Необов'язковий пароль зберігається як короткий застарілий хеш, а не як ключ, що щось шифрує. Будь-хто, хто перейменує файл, відкриє частину і прибере <sheetProtection> рядок читає і редагує все

Тож захист відповідає не на "зупинити колегу від випадкового знищення формули", а на "зберегти ці дані від людини, яка діє свідомо". Це різні проблеми з різними інструментами. Якщо вам потрібна конфіденційність, вам потрібне шифрування на рівні книги, описане в XLSX-вивід із захистом AES, який справді шифрує пакет. Захист аркуша і шифрування книги добре поєднуються, але замком є лише друге. Тримайте цю межу чітко, і решта цієї сторінки - просто технічна обв'язка

Діаграма протиставлення: захист аркуша XLSX у Delphi в HotXLS як політика взаємодії проти шифрування книги AES як єдиного справжнього замка
Захист аркуша відмовляє правки, поки значення лишаються відкритим текстом; лише шифрування книги зашифровує пакет

П'ятнадцять параметрів і властивість AllowOption

Кожен аркуш тепер містить набір TXLSXSheetProtectionOption значень, що описують, що користувач і далі може робити, поки аркуш захищений. Елементи відображаються один до одного у властивості OOXML та прапорцях діалогу Excel:

  • xlsxSpoEditObjects, xlsxSpoEditScenarios - редагування графічних об'єктів і сценаріїв «що буде, якщо»
  • xlsxSpoFormatCells, xlsxSpoFormatColumns, xlsxSpoFormatRows - змінення форматування клітинок, стовпців і рядків
  • xlsxSpoInsertColumns, xlsxSpoInsertRows, xlsxSpoInsertHyperlinks - вставлення стовпців, рядків, посилань
  • xlsxSpoDeleteColumns, xlsxSpoDeleteRows - видалення стовпців і рядків
  • xlsxSpoSelectLockedCells, xlsxSpoSelectUnlockedCells - переміщення виділення на заблоковані або незаблоковані клітинки
  • xlsxSpoSort, xlsxSpoAutoFilter, xlsxSpoPivotTables - сортування діапазонів, використання спадних списків AutoFilter, робота зі зведеними таблицями

Ви читаєте і записуєте окремі біти через індексовану AllowOption властивість на TXLSXWorksheet. AllowOption[Opt] = True означає, що дія дозволена; встановлення цього False забороняє її. Увесь набір також доступний одразу через SheetProtectionOptions, TXLSXSheetProtectionOptions (звичайний Pascal set of), тож ви можете зберегти його, відновити або повністю замінити

За замовчуванням це важливо, і так зроблено навмисно: щойно створений аркуш починає з дозволеними всіма параметрами. Конструктор заповнює 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" щоб означати "форматування дозволено"; ви просто не додаєте цей атрибут. (Значенням за замовчуванням для відсутнього атрибута є булеве значення OOXML true, а ці атрибути названі так, що "true" означає, що відповідне редагування дозволене.)

Записувач HotXLS відтворює це точно. Він виводить sheet="1" щоб увімкнути захист, потім проходить по набору параметрів і записує attr="0" лише для параметрів, які ви встановили в False. Дозволені дії не додають нічого до виходу. Тож книга з попереднього розділу серіалізується приблизно так, зберігаючи лише заборонені дії та хеш пароля:

// Концептуальний вивід для наведеного фрагмента (атрибути опущено для стислості):
// <sheetProtection sheet="1"
//   formatCells="0" formatColumns="0" formatRows="0"
//   insertRows="0" deleteRows="0"
//   password="...4-hex..."/>
// Зверніть увагу, чого ТАМ НЕМАЄ: жодного sort, autoFilter, selectLockedCells.
// Саме їхня відсутність каже Excel, що ці дії залишаються дозволеними.

Якщо ви прийшли зі старого жорстко заданого рядка і чекали побачити кожен атрибут виписаним явно, це виглядає мізерно, майже неправильно. Але це правильно. Файл, у якому були б перелічені sort="1" та autoFilter="1" означав би для сумісного читача те саме, але сам Excel пише мінімальну форму лише із заборонами, а її відтворення зменшує різницю у diff і робить повторне збереження передбачуваним. Атрибути objects та scenarios підкоряються тому самому правилу: за замовчуванням вони дозволені, тож з'являються лише як "0" коли ви їх забороняєте, що є протилежністю старого objects="1" scenarios="1", який виводився безумовно

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

Зчитування захисту назад: точність round-trip

Модель дозволів, яку можна записати, але не прочитати, це дорога в один бік, і типовий симптом тут - цикл завантаження-редагування-збереження, який непомітно розширює дозволи. 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;

Відкрийте файл, який створив записувач, і ви отримаєте Sort та AutoFilter назад як True, FormatCells як False - той самий набір, який ви зберегли, без змін. Саме в цьому й полягає симетрія: змініть одну клітинку на захищеному аркуші з частково дозволеними діями і збережіть знову, і чотирнадцять дозволів, яких ви не чіпали, збережуться замість того, щоб знову звалитися до старого режиму все або нічого

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

Практичні примітки та обмеження

Кілька речей, які варто знати, перш ніж вбудовувати це в конвеєр звітів:

  • Пароль за задумом слабкий. Захист аркуша XLSX зберігає спадковий 16-бітний хеш (той самий, який Excel використовує десятиліттями), і це залишено тут для сумісності. Він відвертає випадкові зміни; він не стримує зловмисника. Не вважайте його сховищем секретів. Для справжнього захисту шифруйте книгу
  • Встановлювати параметри перед захистом можна. AllowOption можна призначати незалежно від того, чи захищено аркуш зараз; перемикачі просто описують, що буде дозволено, коли Protect набуде чинності. UnProtect скидає стан захищеності та хеш, але залишає ваш набір параметрів на місці до наступного разу
  • Семантика заблокованих клітинок усе ще діє. Захист блокує лише редагування клітинок, для яких атрибут Locked встановлено (типове значення книги). Залишити область введення редагованою - це завдання стилю клітинки, а не параметра захисту; ці два рівні поєднуються так само, як в Excel
  • Це механізм XLSX. Модель параметрів повторює старі Allow* властивості механізму XLS, але назви переліку та властивостей тут (xlsxSpo*, AllowOption) належать TXLSXWorksheet у lxHandleX. Якщо ви також керуєте макетом друку на тих самих аркушах, огляд захисту та налаштування сторінки пояснює, як ці параметри співіснують із діапазонами друку та верхніми колонтитулами, а перевірка даних, AutoFilter і таблиці природно поєднується з тим, щоб лишити xlsxSpoAutoFilter відкритим на захищеному звіті

Деталізована модель захисту та решта механізму читання/запису XLSX постачаються в HotXLS Delphi Component для Delphi та C++Builder; на сторінці продукту наведено повний API аркушів, включно з повною довідкою щодо параметрів захисту