مقاله فنی

محافظت از Sheetهای XLSX در Delphi: 15 گزینه Allow

شما یک workbook نهایی را به یک همکار می‌دهید و از او می‌خواهید آن را filter کند، نه اینکه بازنویسی‌اش کند. پس sheet را protect می‌کنید. در نسخه‌های قدیمی HotXLS این حرکت فقط یک چیز را داخل فایل می‌نوشت: <sheetProtection sheet="1" objects="1" scenarios="1"/>، به‌شکل hard-coded، هر بار. sheet قفل می‌شد، hash رمز ضمیمه می‌شد، و کاربر اصلاً هیچ کاری نمی‌توانست بکند، حتی sort و filterی را که واقعاً می‌خواستید باز بماند. خودِ Excel در dialog «Protect Sheet» پانزده checkbox دارد دقیقاً به همین دلیل، و engine هیچ‌کدامشان را نمی‌توانست بیان کند. این فاصله همان چیزی است که model محافظت v2.91.0 آن را می‌بندد

HotXLS یک مؤلفهٔ بومی VCL spreadsheet برای Delphi و C++Builder است که XLS و XLSX را بدون نصب Excel می‌خواند و می‌نویسد. این مقاله دربارهٔ سمت XLSX محافظت worksheet است: enum تازهٔ TXLSXSheetProtectionOption، property AllowOption که هر permission را toggle می‌کند، و تنها قانون encoding در OOXML که هر کسی را که یک <sheetProtection> element را با دست می‌نویسد زمین می‌زند

محافظت worksheet واقعاً چه چیزی را guard می‌کند

اول مرز، چون همین تصمیم می‌دهد چقدر باید به این چیزها اعتماد کنید. محافظت worksheet در فرمت spreadsheet OOXML (ECMA-376) یک policy تعامل است، نه encryption. این به یک application سازگار می‌گوید هنگام protected بودن sheet کدام editها را رد کند. مقدارهای cell هنوز داخل xl/worksheets/sheetN.xml به‌صورت plain text نشسته‌اند؛ .xlsx را unzip کنید و همان‌جا هستند. password اختیاری به‌شکل یک hash قدیمی کوتاه ذخیره می‌شود، نه یک key که چیزی را scramble کند. هر کسی که فایل را rename کند، part را باز کند، و خط <sheetProtection> را بردارد، همه چیز را می‌خواند و ویرایش می‌کند

پس محافظت پاسخ می‌دهد «نذار همکارم تصادفی یک formula را خراب کند»، نه «این داده را از کسی که جدی دنبال آن است مخفی نگه دار». این‌ها مسئله‌های متفاوتی با ابزارهای متفاوت هستند. اگر محرمانگی می‌خواهید، باید سراغ encryption سطح workbook بروید که در پوشش داده شده است، که واقعاً package را encipher می‌کند. محافظت sheet و encryption workbook به‌خوبی با هم ترکیب می‌شوند، اما فقط دومی یک lock است. این خط را صاف نگه دارید و بقیهٔ این صفحه فقط plumbing است

پانزده گزینه و property AllowOption

هر worksheet حالا یک مجموعه از TXLSXSheetProtectionOption values را حمل می‌کند که توضیح می‌دهند کاربر وقتی sheet protected است هنوز چه کارهایی می‌تواند بکند. اعضا یک‌به‌یک به attributeهای OOXML و checkboxهای dialog Excel نگاشت می‌شوند:

  • xlsxSpoEditObjects، xlsxSpoEditScenarios — ویرایش drawing objectها و scenarioهای what-if
  • xlsxSpoFormatCells، xlsxSpoFormatColumns، xlsxSpoFormatRows — قالب‌بندی دوبارهٔ cellها، ستون‌ها، ردیف‌ها
  • xlsxSpoInsertColumns، xlsxSpoInsertRows، xlsxSpoInsertHyperlinks — درج ستون‌ها، ردیف‌ها، linkها
  • xlsxSpoDeleteColumns، xlsxSpoDeleteRows — حذف ستون‌ها، ردیف‌ها
  • xlsxSpoSelectLockedCells، xlsxSpoSelectUnlockedCells — جابه‌جا کردن selection روی cellهای locked یا unlocked
  • xlsxSpoSort، xlsxSpoAutoFilter، xlsxSpoPivotTables — مرتب‌سازی rangeها، استفاده از dropdownهای AutoFilter، کار با PivotTableها

شما بیت‌های فرد را از طریق property indexی AllowOption روی TXLSXWorksheet می‌خوانید و می‌نویسید. AllowOption[Opt] = True یعنی آن action مجاز است؛ تنظیم آن False یعنی آن را ممنوع می‌کند. کل مجموعه را هم می‌توان یک‌جا از طریق SheetProtectionOptions، یک TXLSXSheetProtectionOptions (یک Pascal سادهٔ set of) هم می‌توان به آن رسید، پس می‌توانید آن را ذخیره کنید، برگردانید، یا یک‌جا جایگزینش کنید

اهمیت default و عمدی بودن آن: یک worksheet تازه‌ساخته‌شده با همهٔ گزینه‌ها مجاز شروع می‌کند. سازنده SheetProtectionOptions را با بازهٔ کامل seed می‌کند، [Low(TXLSXSheetProtectionOption)..High(TXLSXSheetProtectionOption)]. از آن‌جا به بعد با حذف actionهایی که می‌خواهید ممنوع کنید جلو می‌روید، نه اینکه از صفر یک permission set بسازید. همین انتخاب است که rule encoding نویسنده، پایین‌تر، را با رفتار Excel هم‌راستا می‌کند

محافظت از یک sheet اما باز گذاشتن sort و filter

این هم حالت رایج از ابتدا تا انتها: یک report نهایی را protect کنید تا layout آن نتواند reshaped شود، اما به خواننده اجازه دهید آن را sort و filter کند. توجه کنید که Protect و options مستقل‌اند. Protect sheet را به حالت protected می‌برد و hash اختیاری رمز را ذخیره می‌کند؛ به مجموعهٔ options دست نمی‌زند. شما AllowOption را جداگانه تنظیم می‌کنید، و toggleها وقتی sheet protected و saved شود اثر می‌گذارند

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;

دو چیز از آن snippet ارزش توجه دارد. Sort و AutoFilter خط‌ها با وجود اینکه هر دو روی True پیش‌فرض هستند، صریح نوشته می‌شوند؛ این documentation برای maintainer بعدی است، نه یک نیاز functional. و چون defaultها permissive هستند، تنها خط‌هایی که فایل خروجی را تغییر می‌دهند همان‌هایی‌اند که یک option را به False تنظیم می‌کنند. این اتفاقی در این API نیست، بلکه wire format OOXML است که خودش را نشان می‌دهد، و همین به بخش بعدی می‌رسد

قانون encoding: حذف یعنی اجازه، attr=0 یعنی منع

این تنها fact خلاف‌انتظار در کل این feature است، و جایی است که OOXML دست‌نویس معمولاً اشتباه می‌شود. در OOXML هر attribute مربوط به هر action یک <sheetProtection> است منع flag، و نبودنش یعنی permission. یک attribute که غایب است یعنی action مجاز است. یک attribute که به صورت "0" نوشته شده یعنی action وقتی sheet protected است ممنوع است. در یک فایل well-formed هیچ formatCells="1"ی برای معنی «قالب‌بندی مجاز است» وجود ندارد؛ شما فقط attribute را حذف می‌کنید. (default برای یک attribute غایب، default بولی OOXML یعنی true است، و این attributeها طوری نام‌گذاری شده‌اند که «true» یعنی edit متناظر مجاز است.)

Writer در HotXLS دقیقاً همان را mirror می‌کند. sheet="1" را می‌نویسد تا protection را روشن کند، سپس مجموعهٔ options را walk می‌کند و attr="0" فقط برای گزینه‌هایی می‌نویسد که به False تنظیم کرده‌اید. actionهای مجاز هیچ چیزی به output اضافه نمی‌کنند. پس workbook بخش قبلی چیزی شبیه این serializes می‌شود، و فقط actionهای ممنوع و hash رمز را حمل می‌کند:

// 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.

اگر از رشتهٔ قدیمی hard-coded آمده باشید و انتظار داشته باشید هر attribute به‌صراحت نوشته شود، این sparse به نظر می‌رسد، تقریباً غلط. اما درست است. فایلی که sort="1" و autoFilter="1" را فهرست کرده باشد برای یک reader سازگار همان معنی را می‌دهد، اما خود Excel حداقل‌نویسی forbid-only را می‌نویسد، و هم‌سان‌سازی با آن diffها را کوچک و round-tripها را boring نگه می‌دارد. objects و scenarios attributeها همان rule را دنبال می‌کنند: آن‌ها default-permitted هستند، پس فقط وقتی به صورت "0" ظاهر می‌شوند که آن‌ها را ممنوع کنید، که برعکسِ objects="1" scenarios="1" قدیمی است که بدون شرط emit می‌شد

خواندن محافظت به عقب: fidelity در round-trip

یک permission model که بتوانید آن را بنویسید اما نتوانید بخوانید، یک درِ یک‌طرفه است، و symptom معمول load-edit-save چرخه‌ای است که بی‌صدا permissions را گسترش می‌دهد. HotXLS این را می‌بندد. وقتی ParseWorksheetXml به یک <sheetProtection> element می‌رسد، sheet را protected می‌کند، hash رمز را اگر موجود باشد می‌گیرد، و سپس هر attribute هر action را به AllowOptionآیند، با همان convention برعکس: attributeی که present است و برابر "0" است action را ممنوع می‌کند؛ attribute غایب option را روی default مجازش می‌گذارد

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;

وقتی فایلی را که writer تولید کرده load کنید، Sort و AutoFilter را به صورت True، FormatCells را به صورت False پس می‌گیرید، یعنی مجموعه‌ای که save کرده بودید intact برمی‌گردد. این symmetry کل هدف است: یک cell را در یک sheet protected و partially-permitted ویرایش کنید و دوباره save کنید، و آن چهارده permissionی که دست نزده بودید به‌جای برگشتن به default قدیمی all-or-nothing باقی بمانند

نکات عملی و محدودیت‌ها

قبل از اینکه این را وارد pipeline گزارش کنید، چند چیز ارزش دانستن دارند:

  • رمز به‌صورت عمدی ضعیف است. worksheet protection در XLSX یک legacy hash شانزده‌بیتی را ذخیره می‌کند (همان چیزی که Excel دهه‌هاست استفاده کرده)، و اینجا برای interoperability نگه داشته شده است. این فقط edits اتفاقی را بازمی‌دارد؛ در برابر یک attacker مقاومت نمی‌کند. آن را secret نگه‌دارنده فرض نکنید. برای حفاظت واقعی، workbook را encrypt کنید
  • تنظیم options پیش از protect کردن کاملاً درست است. AllowOption را می‌توان چه sheet در حال حاضر protected باشد چه نباشد assign کرد؛ toggleها فقط توصیف می‌کنند که protection چه چیزهایی را وقتی Protect فعال است مجاز خواهد کرد. UnProtect state protected و hash را پاک می‌کند اما مجموعهٔ options شما را برای دفعهٔ بعد سر جایش می‌گذارد
  • معنای cell قفل‌شده همچنان برقرار است. protection فقط editهایی را روی cellهایی block می‌کند که attribute Locked آن‌ها set شده باشد (default workbook). editable گذاشتن یک input region کار style cell است، نه یک option محافظت؛ این دو لایه مثل Excel با هم ترکیب می‌شوند
  • این موتور XLSX است. model گزینه‌ها با propertyهای قدیمی‌تر موتور XLS Allow* را mirror می‌کند، اما نام‌های enum و property اینجا (xlsxSpo*, AllowOption) متعلق به TXLSXWorksheet در lxHandleX. اگر روی همین sheetها layout چاپ را هم کنترل می‌کنید، walkthrough محافظت و page-setup توضیح می‌دهد این تنظیم‌ها کنار print areaها و headerها چگونه می‌نشینند، و data validation، AutoFilter و tables طبیعی با باز گذاشتن xlsxSpoAutoFilter روی یک report قفل‌شده جفت می‌شود

مدل محافظت دقیق و بقیهٔ engine read/write XLSX در HotXLS Component برای Delphi و C++Builder ship می‌شوند؛ صفحهٔ محصول reference کامل worksheet API شامل مرجع کامل optionهای محافظت را دارد