Teknisk artikkel

XLSX-arkbeskyttelse i Delphi: 15 tillatelsesvalg

Du sender en ferdig arbeidsbok til en kollega og ber vedkommende filtrere den, ikke skrive den om. Så du beskytter arket. I eldre HotXLS-bygger skrev den gesten én ting inn i filen: <sheetProtection sheet="1" objects="1" scenarios="1"/>, hardkodet, hver gang. Arket ble låst, passordhashet fulgte med, og brukeren kunne ikke gjøre noe som helst, ikke engang sorteringen og filteret du faktisk ville la stå åpent. Excels egen dialog for «Beskytte ark» har femten avkrysningsbokser av akkurat denne grunnen, og motoren kunne ikke uttrykke en eneste av dem. Det er dette hullet beskyttelsesmodellen i v2.91.0 lukker

HotXLS er en innfødt VCL-regnearkkomponent for Delphi og C++Builder som leser og skriver XLS og XLSX uten at Excel er installert. Denne artikkelen handler om XLSX-siden av arkbeskyttelse: den nye TXLSXSheetProtectionOption-enumen, AllowOption-egenskapen som slår hver tillatelse av og på, og den ene OOXML-kodingsregelen som feller alle som skriver et <sheetProtection>-element for hånd

Hva arkbeskyttelse faktisk beskytter

Først avgrensningen, fordi den bestemmer hvor mye du skal stole på noe av dette. Arkbeskyttelse i OOXML-regnearkformatet (ECMA-376) er en interaksjonspolicy, ikke kryptering. Den forteller en kompatibel applikasjon hvilke redigeringer den skal avvise mens arket er beskyttet. Celleverdiene ligger fortsatt som ren tekst i xl/worksheets/sheetN.xml; pakk opp .xlsx-filen, så er de rett der. Det valgfrie passordet lagres som en kort, eldre hash, ikke som en nøkkel som krypterer noe som helst. Alle som gir filen nytt navn, åpner delen og fjerner <sheetProtection>-linjen, kan lese og redigere alt

Så beskyttelse svarer på «stopp kollegaen min fra å ødelegge en formel ved et uhell», ikke «hold denne dataen hemmelig for noen som virkelig vil ha den». Det er ulike problemer med ulike verktøy. Hvis du trenger konfidensialitet, vil du ha krypteringen på arbeidsboknivå som dekkes i AES-beskyttet XLSX-utdata, som faktisk krypterer pakken. Arkbeskyttelse og arbeidsbokkryptering fungerer fint sammen, men bare den andre er en lås. Hold den forskjellen klar, så er resten av siden bare rørføring

De femten valgene og AllowOption-egenskapen

Hvert arbeidsark bærer nå et sett med TXLSXSheetProtectionOption-verdier som beskriver hva brukeren fortsatt kan gjøre mens arket er beskyttet. Medlemmene mapper én til én mot OOXML-attributtene og Excel-dialogens avkrysningsbokser:

  • xlsxSpoEditObjects, xlsxSpoEditScenarios - rediger tegneobjekter og hva-hvis-scenarier
  • xlsxSpoFormatCells, xlsxSpoFormatColumns, xlsxSpoFormatRows - formater celler, kolonner og rader på nytt
  • xlsxSpoInsertColumns, xlsxSpoInsertRows, xlsxSpoInsertHyperlinks - sett inn kolonner, rader og lenker
  • xlsxSpoDeleteColumns, xlsxSpoDeleteRows - slett kolonner og rader
  • xlsxSpoSelectLockedCells, xlsxSpoSelectUnlockedCells - flytt markeringen til låste eller ulåste celler
  • xlsxSpoSort, xlsxSpoAutoFilter, xlsxSpoPivotTables - sorter områder, bruk AutoFilter-rullegardiner og arbeid med pivottabeller

Du leser og skriver individuelle biter gjennom den indekserte AllowOption-egenskapen på TXLSXWorksheet. AllowOption[Opt] = True betyr at handlingen er tillatt; setter du den til False, blir den forbudt. Hele settet er også tilgjengelig på én gang gjennom SheetProtectionOptions, et TXLSXSheetProtectionOptions (et vanlig Pascal set of), så du kan lagre det, gjenopprette det eller erstatte det helt

Standardverdien betyr noe, og det er bevisst: Et nyopprettet arbeidsark starter med alle valgene tillatt. Konstruktøren fyller SheetProtectionOptions med hele intervallet, [Low(TXLSXSheetProtectionOption)..High(TXLSXSheetProtectionOption)]. Du snevrer inn derfra ved å ta ut de handlingene du vil forby, i stedet for å bygge et tillatelsessett fra bunnen av. Det valget er det som gjør skriverens kodingsregel nedenfor i tråd med Excels oppførsel

Beskytte et ark, men la sortering og filter være åpne

Her er det vanlige tilfellet fra ende til ende: beskytt en ferdig rapport slik at layouten ikke kan omformes, men la leseren sortere og filtrere den. Legg merke til at Protect og valgene er uavhengige. Protect setter arket i beskyttet tilstand og lagrer den valgfrie passordhasheten; den rører ikke valgssettet. Du justerer AllowOption separat, og valgene trer i kraft når arket er beskyttet og lagret

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;

To ting er verdt å lese ut av dette utdraget. Linjene for Sort og AutoFilter er skrevet eksplisitt selv om begge har standardverdien True; det er dokumentasjon for neste vedlikeholder, ikke et funksjonelt krav. Og siden standardverdiene er tillatende, er de eneste linjene som endrer utdatafilen, de som setter et valg til False. Det er ikke en tilfeldighet i dette API-et, det er OOXML-wireformatet som synes, og det er neste seksjon

Kodingsregelen: utelatt betyr tillatt, attr=0 betyr forbudt

Dette er den eneste motintuitive detaljen i hele funksjonen, og det er her håndskrevne <sheetProtection>-elementer som regel går galt. I OOXML er hvert per-handling-attributt et forby-flagg, og fraværet betyr tillatelse. Et attributt som mangler, betyr at handlingen er tillatt. Et attributt skrevet som "0", betyr at handlingen er forbudt mens arket er beskyttet. Det finnes ikke noe formatCells="1" i en vel dannet fil som betyr «formatering er tillatt»; du lar ganske enkelt attributtet være ute. Standardverdien for et utelatt attributt er OOXMLs boolske standardverdi true, og disse attributtene er navngitt slik at true betyr at den aktuelle redigeringen er tillatt

HotXLS-skriveren gjenspeiler dette helt nøyaktig. Den skriver sheet="1" for å slå på beskyttelsen, går deretter gjennom valgssettet og skriver attr="0" bare for valgene du setter til False. Tillatte handlinger bidrar ikke med noe til utdata. Så arbeidsboken fra forrige seksjon serialiseres omtrent slik, med bare de forbudte handlingene og passordhasheten:

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

Hvis du kommer fra den gamle hardkodede strengen og forventer å se hvert attributt skrevet ut, ser dette sparsomt ut, nesten feil. Det er riktig. En fil som listet sort="1" og autoFilter="1", ville bety det samme for en kompatibel leser, men Excel selv skriver den minimale formen som bare uttrykker forbud, og å matche den holder diffene små og rundturene udramatiske. objects- og scenarios-attributtene følger samme regel: de er tillatt som standard, så de dukker bare opp som "0" når du forbyr dem, som er motsatsen til de gamle objects="1" scenarios="1" som ble skrevet ut uten betingelser

Lese beskyttelsen tilbake: rundturstrofasthet

En tillatelsesmodell du kan skrive, men ikke lese, er en enveiskjørt gate, og det vanlige symptomet er en last-rediger-lagre-syklus som stille utvider tillatelsene. HotXLS lukker den døren. Når ParseWorksheetXml treffer et <sheetProtection>-element, setter den arket som beskyttet, fanger passordhasheten hvis den finnes, og dekoder deretter hvert per-handling-attributt tilbake til AllowOption med samme konvensjon motsatt vei: et attributt som er til stede og lik "0", forbyr handlingen; et utelatt attributt lar valget stå på standardverdien, altså tillatt

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;

Laster du filen skriveren produserte, får du Sort og AutoFilter tilbake som True, FormatCells som False - det settet du lagret, intakt. Den symmetrien er hele poenget: rediger én celle i et beskyttet, delvis tillatt ark og lagre på nytt, og de fjorten tillatelsene du ikke rørte, overlever i stedet for å falle tilbake til den gamle alt-eller-ingenting-standardverdien

Praktiske merknader og begrensninger

Noen ting er verdt å vite før du kobler dette inn i en rapportflyt:

  • Passordet er svakt med vilje. XLSX-arkbeskyttelse lagrer en 16-bit eldre hash, den samme Excel har brukt i flere tiår, og den beholdes her for kompatibilitet. Den hindrer tilfeldige endringer, men den står ikke imot en angriper. Ikke bruk den som hemmelighetsvern. For ekte beskyttelse må du kryptere arbeidsboken
  • Det er greit å sette valgene før du beskytter. AllowOption kan tildeles enten arket er beskyttet eller ikke; valgene beskriver bare hva beskyttelsen vil tillate når Protect er i kraft. UnProtect rydder bort beskyttet tilstand og hash, men lar valgssettet ligge klart til neste gang
  • Semantikken for låste celler gjelder fortsatt. Beskyttelse blokkerer bare redigering av celler der Locked-attributtet er satt, som er arbeidsbokstandard. Å gjøre et inndataområde redigerbart er et spørsmål om cellestil, ikke et beskyttelsesvalg; de to lagene virker sammen på samme måte som i Excel
  • Dette er XLSX-motoren. Valgsmodellen speiler de eldre Allow*-egenskapene i XLS-motoren, men enum- og egenskapsnavnene her, xlsxSpo* og AllowOption, tilhører TXLSXWorksheet i lxHandleX. Hvis du også styrer utskriftslayout på de samme arkene, dekker gjennomgangen av beskyttelse og sideoppsett hvordan disse innstillingene ligger sammen med utskriftsområder og topp- og bunntekster, og datavalidering, AutoFilter og tabeller passer naturlig sammen med å la xlsxSpoAutoFilter være åpen på en låst rapport

Den finmaskede beskyttelsesmodellen og resten av XLSX-lese- og skrivemotoren følger med i HotXLS Component for Delphi og C++Builder; produktsiden har hele arbeidsark-API-et, inkludert den fullstendige referansen for beskyttelsesvalg