Techninis straipsnis

XLSX darbalapio apsaugos leidimų valdymas Delphi aplinkoje su HotXLS

Jūs perduodate baigtą darbaknygę kolegai ir paprašote ją filtruoti, o ne perrašyti. Todėl užrakinate lapą. Senesnėse HotXLS versijose šis veiksmas į failą visada įrašydavo vieną ir tą patį dalyką: <sheetProtection sheet="1" objects="1" scenarios="1"/>, kietai užkoduotą, kiekvieną kartą. Lapas užsirakindavo, slaptažodžio hash prisikabindavo, o naudotojas negalėdavo nieko - net rūšiuoti ar filtruoti, nors būtent tai jūs ir norėjote palikti atvirą. Būtent dėl šios priežasties pačiame Excel dialoge "Protect Sheet" yra penkiolika varnelių, o variklis negalėjo išreikšti nė vienos iš jų. Šią spragą ir uždaro v2.91.0 apsaugos modelis

HotXLS yra vietinis VCL skaičiuoklių komponentas, skirtas Delphi ir C++Builder, skaitantis ir rašantis XLS bei XLSX be įdiegto Excel. Šis straipsnis skirtas XLSX pusės darbalapio apsaugai: naujajam enum TXLSXSheetProtectionOption, savybei AllowOption, kuri perjungia kiekvieną leidimą, ir vienai OOXML kodavimo taisyklei, kuri suklumpa beveik kiekvienam, bandančiam ranka parašyti <sheetProtection> elementą

Ką darbalapio apsauga iš tikrųjų saugo

Pirmiausia - riba, nes ji nusprendžia, kiek visa tai verta pasitikėjimo. Darbalapio apsauga OOXML skaičiuoklių formate (ECMA-376) yra sąveikos politika, o ne šifravimas. Ji nurodo specifikaciją atitinkančiai programai, kurių redagavimų ji turi atsisakyti, kai lapas yra apsaugotas. Langelių reikšmės vis dar guli xl/worksheets/sheetN.xml faile paprastu tekstu; išarchyvuokite .xlsx ir jos bus ten. Pasirenkamas slaptažodis saugomas kaip trumpas senasis hash, o ne raktas, kuris ką nors užšifruotų. Bet kas, kas pervadina failą, atidaro dalį ir pašalina eilutę <sheetProtection>, gali viską skaityti ir redaguoti

Taigi apsauga atsako į klausimą "kaip neleisti kolegai netyčia sugadinti formulės", o ne į klausimą "kaip išlaikyti šiuos duomenis paslaptyje nuo motyvuoto žmogaus". Tai skirtingos problemos, reikalaujančios skirtingų įrankių. Jei jums reikia konfidencialumo, jums reikia darbaknygės lygio šifravimo, aptarto AES-protected XLSX output straipsnyje, kuris iš tikrųjų užšifruoja paketą. Darbalapio apsauga ir darbaknygės šifravimas lengvai dera tarpusavyje, tačiau tik antrasis iš jų yra tikras užraktas. Jei šią ribą laikysite aiškiai, visa likusi šio puslapio dalis yra tik santechnika

Penkiolika parinkčių ir AllowOption savybė

Kiekvienas darbalapis dabar turi TXLSXSheetProtectionOption reikšmių rinkinį, aprašantį, ką naudotojas dar gali daryti, kol lapas apsaugotas. Nariai vienas prie vieno atitinka OOXML atributus ir Excel dialogo varneles:

  • xlsxSpoEditObjects, xlsxSpoEditScenarios - redaguoti piešimo objektus ir what-if scenarijus
  • xlsxSpoFormatCells, xlsxSpoFormatColumns, xlsxSpoFormatRows - performatuoti langelius, stulpelius ir eilutes
  • xlsxSpoInsertColumns, xlsxSpoInsertRows, xlsxSpoInsertHyperlinks - įterpti stulpelius, eilutes ir nuorodas
  • xlsxSpoDeleteColumns, xlsxSpoDeleteRows - trinti stulpelius ir eilutes
  • xlsxSpoSelectLockedCells, xlsxSpoSelectUnlockedCells - perkelti pažymėjimą į užrakintus arba neužrakintus langelius
  • xlsxSpoSort, xlsxSpoAutoFilter, xlsxSpoPivotTables - rūšiuoti sritis, naudoti AutoFilter išskleidžiamuosius sąrašus ir dirbti su PivotTables

Atskirus bitus skaitote ir rašote per indeksuotą AllowOption savybę objekte TXLSXWorksheet. AllowOption[Opt] = True reiškia, kad veiksmas leidžiamas; nustatymas į False jį uždraudžia. Visas rinkinys taip pat vienu kartu pasiekiamas per SheetProtectionOptions, tai yra TXLSXSheetProtectionOptions (paprastas Pascal set of), todėl galite jį išsaugoti, atkurti arba pakeisti visą iš karto

Numatytoji būsena svarbi ir sąmoningai pasirinkta: naujai sukurtas darbalapis pradeda nuo visų leidžiamų parinkčių. Konstruktorius užpildo SheetProtectionOptions visu intervalu [Low(TXLSXSheetProtectionOption)..High(TXLSXSheetProtectionOption)]. Toliau jūs siaurinate, išmesdami veiksmus, kuriuos norite uždrausti, užuot kūrę leidimų rinkinį nuo nulio. Būtent šis pasirinkimas leidžia toliau aprašomai rašyklės kodavimo taisyklei sutapti su Excel elgesiu

Lapo apsauga paliekant rūšiavimą ir filtravimą atvirus

Štai dažniausias scenarijus nuo pradžios iki galo: apsaugoti baigtą ataskaitą taip, kad išdėstymas nebūtų perkonstruotas, tačiau leisti skaitytojui ją rūšiuoti ir filtruoti. Atkreipkite dėmesį, kad Protect ir parinktys yra nepriklausomos. Protect perjungia lapą į apsaugotą būseną ir išsaugo pasirenkamą slaptažodžio hash; jis neliečia parinkčių rinkinio. AllowOption reguliuojate atskirai, o perjungimai pradeda galioti tada, kai lapas apsaugomas ir išsaugomas

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;

Iš to pavyzdžio verta perskaityti du dalykus. Eilutės Sort ir AutoFilter parašytos aiškiai, nors abi pagal nutylėjimą yra True; tai dokumentacija kitam prižiūrėtojui, o ne funkcinis reikalavimas. Ir kadangi numatytosios reikšmės yra leidžiančios, vienintelės eilutės, pakeičiančios išvesties failą, yra tos, kurios kurią nors parinktį nustato į False. Tai nėra šio API atsitiktinumas - tiesiog per jį prasimuša OOXML vielinis formatas, apie kurį ir kalba kita skiltis

Kodavimo taisyklė: praleista reiškia leista, attr=0 reiškia uždrausta

Tai vienintelis tikrai priešintuityvus faktas visoje funkcijoje ir būtent ta vieta, kur ranka rašomas <sheetProtection> dažniausiai suklysta. OOXML kiekvienas veiksmą valdantis atributas yra uždraudimo vėliava, o jo nebuvimas reiškia leidimą. Jei atributo nėra, veiksmas leidžiamas. Jei atributas įrašytas kaip "0", tai reiškia, kad veiksmas draudžiamas, kol lapas apsaugotas. Tvarkingame faile nėra formatCells="1", reiškiančio, kad formatavimas leidžiamas; jūs tiesiog paliekate atributą neišrašytą. (Numatytoji neegzistuojančio atributo reikšmė yra OOXML loginio tipo numatytoji true, o atributų pavadinimai suformuluoti taip, kad "true" reikštų atitinkamo redagavimo leidimą.)

HotXLS rašyklė tai atkartoja tiksliai. Ji išrašo sheet="1", kad įjungtų apsaugą, tada pereina per parinkčių rinkinį ir išrašo attr="0" tik toms parinktims, kurias nustatėte į False. Leidžiami veiksmai į išvestį nieko neprideda. Todėl ankstesniame skyriuje aprašyta darbaknygė serializuojama maždaug taip, įtraukdama tik uždraustus veiksmus ir slaptažodžio 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.

Jei atėjote iš senojo kietai užkoduoto teksto ir tikėjotės matyti išvardytą kiekvieną atributą, ši forma atrodo reta, beveik neteisinga. Ji yra teisinga. Failas, kuriame būtų sort="1" ir autoFilter="1", atitinkančiam skaitytuvui reikštų tą patį, tačiau pats Excel rašo minimalią formą, kurioje išvardijami tik draudimai, o jos laikymasis išlaiko diff mažus ir round-trip nuobodžius. Atributai objects ir scenarios laikosi tos pačios taisyklės: jie pagal nutylėjimą leidžiami, todėl pasirodo tik kaip "0", kai juos uždraudžiate - tai yra priešingybė senajam objects="1" scenarios="1", kuris buvo generuojamas besąlygiškai

Apsaugos nuskaitymas atgal: round-trip tikslumas

Leidimų modelis, kurį galima parašyti, bet negalima perskaityti, yra vienkryptės durys, o tipinis simptomas - load-edit-save ciklas, kuris tyliai išplečia leidimus. HotXLS tai uždaro. Kai ParseWorksheetXml aptinka <sheetProtection> elementą, jis pažymi lapą kaip apsaugotą, pasiima slaptažodžio hash, jei toks yra, ir tada kiekvieną veiksmą valdantį atributą iškoduoja atgal į AllowOption, naudodamas tą pačią taisyklę atbulai: atributas, kuris egzistuoja ir lygus "0", uždraudžia veiksmą; neegzistuojantis atributas palieka parinktį numatytoje leidžiančioje būsenoje

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;

Įkelkite failą, kurį sukūrė rašyklė, ir Sort bei AutoFilter grįš kaip True, o FormatCells kaip False - lygiai tas pats rinkinys, kurį išsaugojote. Būtent ši simetrija ir yra esmė: pakeiskite vieną langelį apsaugotame lape su daliniais leidimais ir išsaugokite jį iš naujo, ir keturiolika leidimų, kurių nelietėte, išliks, užuot sugriuvę atgal į senąją viskas-arba-nieko numatytąją būseną

Praktinės pastabos ir ribos

Keli dalykai, kuriuos verta žinoti prieš įpinant tai į ataskaitų vamzdyną:

  • Slaptažodis yra silpnas pagal sumanymą. XLSX darbalapio apsauga saugo 16 bitų senąjį hash (tą patį, kurį Excel naudoja dešimtmečius), paliktą dėl suderinamumo. Jis atgraso nuo atsitiktinių pakeitimų; jis neatsilaiko prieš puolėją. Nelaikykite jo paslapties saugotoju. Tikrai apsaugai užšifruokite darbaknygę
  • Parinktis nustatyti prieš įjungiant apsaugą yra normalu. AllowOption galima priskirti nepriklausomai nuo to, ar lapas tuo metu apsaugotas; perjungimai tik aprašo, ką apsauga leis tada, kai pradės veikti Protect. UnProtect nuima apsaugotą būseną ir hash, bet palieka jūsų parinkčių rinkinį kitam kartui
  • Užrakintų langelių semantika vis tiek galioja. Apsauga blokuoja tik langelių, kurių Locked atributas nustatytas (numatytoji darbaknygės būsena), redagavimą. Palikti įvesties sritį redaguojamą yra langelio stiliaus užduotis, o ne apsaugos parinktis; abu sluoksniai susideda taip pat, kaip ir Excel
  • Čia kalbame apie XLSX variklį. Parinkčių modelis atkartoja senesnes XLS variklio Allow* savybes, tačiau čia esantys enum ir savybių vardai (xlsxSpo*, AllowOption) priklauso TXLSXWorksheet objekte lxHandleX. Jei tuose pačiuose lapuose taip pat valdote spausdinimo išdėstymą, apsaugos ir puslapio nustatymų apžvalga parodo, kaip šie nustatymai dera su spausdinimo sritimis ir antraštėmis, o duomenų validacija, AutoFilter ir lentelės natūraliai dera su paliktu atviru xlsxSpoAutoFilter užrakintoje ataskaitoje

Smulkiai valdoma apsaugos schema ir visas likęs XLSX skaitymo bei rašymo variklis tiekiami kartu su HotXLS Component, skirtu Delphi ir C++Builder; produkto puslapyje pateikiamas visas darbalapio API, įskaitant pilną apsaugos parinkčių nuorodyną