Teknisk artikel

XLSX-arkbeskyttelse i Delphi: 15 tilladelsesvalg

Du giver en færdig projektmappe til en kollega og beder dem filtrere den, ikke omskrive den. Så beskytter du arket. I ældre HotXLS-builds skrev den gestus én ting ind i filen: <sheetProtection sheet="1" objects="1" scenarios="1"/>, hard-coded, hver gang. Arket låste, password-hashen blev lagt på, og brugeren kunne ikke gøre noget som helst, ikke engang de sorterings- og filterhandlinger, du faktisk ville lade stå åbne. Excels egen dialogboks "Protect Sheet" har femten afkrydsningsfelter af netop denne grund, og motoren kunne ikke udtrykke nogen af dem. Det hul er, hvad v2.91.0-beskyttelsesmodellen lukker

HotXLS er en native VCL spreadsheet-komponent til Delphi og C++Builder, som læser og skriver XLS og XLSX uden at Excel er installeret. Denne artikel handler om XLSX-siden af arkbeskyttelse: den nye TXLSXSheetProtectionOption-enum, AllowOption-egenskaben, der slår hver enkelt tilladelse til og fra, og den ene OOXML-kodningsregel, der snubler alle, der skriver et <sheetProtection>-element i hånden

Hvad arkbeskyttelse faktisk beskytter

Først grænsen, fordi den afgør, hvor meget du bør stole på noget af dette. Arkbeskyttelse i OOXML-regnearksformatet (ECMA-376) er en interaktionspolitik, ikke kryptering. Den fortæller et konformt program, hvilke redigeringer det skal afvise, mens arket er beskyttet. Celleværdierne ligger stadig i xl/worksheets/sheetN.xml i klartekst; udpak .xlsx-filen, og de er lige der. Den valgfrie adgangskode gemmes som en kort, forældet hash, ikke en nøgle, der forvrænger noget. Enhver, der omdøber filen, åbner delen og fjerner linjen <sheetProtection>, kan læse og redigere alt

Så svarer beskyttelse på "forhindr min kollega i ved et uheld at smadre en formel," ikke "hold disse data hemmelige for nogen, der er motiveret." Det er to forskellige problemer med to forskellige værktøjer. Har du brug for fortrolighed, skal du bruge krypteringen på projektmappeniveau, der er beskrevet i AES-beskyttet XLSX-output, som rent faktisk krypterer pakken. Arkbeskyttelse og projektmappekryptering kombineres uden problemer, men kun den anden er en lås. Hold den skillelinje klar, så er resten af denne side bare rørføring

Diagram, der kontrasterer Delphi XLSX arkbeskyttelse i HotXLS som en interaktionspolitik med AES projektmappenkryptering som den eneste ægte lås
Arkbeskyttelse nægter redigeringer, mens værdierne forbliver ren tekst; kun projektmappekryptering krypterer pakken

De femten valg og AllowOption-egenskaben

Hvert regneark har nu et sæt TXLSXSheetProtectionOption-værdier, der beskriver, hvad brugeren stadig må gøre, mens arket er beskyttet. Medlemmerne svarer én til én til OOXML-attributterne og til afkrydsningsfelterne i Excel-dialogen:

  • xlsxSpoEditObjects, xlsxSpoEditScenarios — rediger tegneobjekter og hvad-nu-hvis-scenarier
  • xlsxSpoFormatCells, xlsxSpoFormatColumns, xlsxSpoFormatRows — omformatér celler, kolonner, rækker
  • xlsxSpoInsertColumns, xlsxSpoInsertRows, xlsxSpoInsertHyperlinks — indsæt kolonner, rækker, links
  • xlsxSpoDeleteColumns, xlsxSpoDeleteRows — slet kolonner, rækker
  • xlsxSpoSelectLockedCells, xlsxSpoSelectUnlockedCells — flyt markeringen hen på låste eller ulåste celler
  • xlsxSpoSort, xlsxSpoAutoFilter, xlsxSpoPivotTables — sortér områder, brug AutoFilter-rullelister, arbejd med pivottabeller

Du læser og skriver individuelle bits gennem den indekserede AllowOption-egenskab på TXLSXWorksheet. AllowOption[Opt] = True betyder, at handlingen er tilladt; sætter du den til False, forbydes den. Hele sættet kan også nås på én gang gennem SheetProtectionOptions, en TXLSXSheetProtectionOptions (et almindeligt Pascal set of), så du kan gemme det, gendanne det eller udskifte det som helhed

Standardværdien betyder noget, og den er bevidst: et nyoprettet regneark starter med hver eneste indstilling tilladt. Konstruktøren fylder SheetProtectionOptions med hele intervallet, [Low(TXLSXSheetProtectionOption)..High(TXLSXSheetProtectionOption)]. Du indsnævrer derfra ved at udelukke de handlinger, du vil forbyde, i stedet for at bygge et tilladelsessæt op fra bunden. Det valg er, hvad der får writerens kodningsregel, nedenfor, til at stemme overens med Excels adfærd

Beskyt et ark, men lad sortering og filtrering være åben

Her er det almindelige tilfælde fra ende til anden: beskyt en færdig rapport, så layoutet ikke kan omformes, men lad læseren sortere og filtrere den. Bemærk, at Protect og indstillingerne er uafhængige. Protect slår arket over i den beskyttede tilstand og gemmer den valgfrie adgangskode-hash; den rører ikke ved indstillingssættet. Du justerer AllowOption separat, og skifterne træder i kraft, så snart arket er beskyttet og gemt

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;

    // Beskyt med en adgangskode. Dette sætter kun den beskyttede tilstand + hash;
    // indstillingssættet efterlades i sin standard, hvor alt er tilladt.
    sh.Protect('HotXLS-2026');

    // Indsnævr: behold sort + AutoFilter, forbyd omformning og omformatering.
    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 at læse ud af det udsnit. Linjerne med Sort og AutoFilter skrives eksplicit, selvom begge som standard er True; det er dokumentation til den næste vedligeholder, ikke et funktionelt krav. Og fordi standarderne er tilladende, er det kun linjerne, der sætter en indstilling til False, der ændrer outputfilen. Det er ikke et tilfælde ved denne API, det er OOXML-wireformatet, der skinner igennem, hvilket er næste afsnit

Kodningsreglen: udeladelse betyder tilladelse, attr=0 betyder forbud

Det er den ene kontraintuitive kendsgerning i hele funktionen, og det er der, hvor håndskrevet <sheetProtection> normalt går galt. I OOXML er hver pr.-handling-attribut et forbud-flag, og dets fravær er tilladelse. En attribut, der mangler, betyder, at handlingen er tilladt. En attribut skrevet som "0" betyder, at handlingen er forbudt, mens arket er beskyttet. Der findes ikke noget formatCells="1" i en velformet fil for at betyde "formatering er tilladt"; du udelader simpelthen attributten. (Standarden for en manglende attribut er OOXML's boolske standard true, og disse attributter er navngivet, så "true" betyder, at den tilsvarende redigering er tilladt.)

HotXLS-writeren spejler det nøjagtigt. Den udsender sheet="1" for at slå beskyttelse til, gennemgår derefter indstillingssættet og skriver attr="0" kun for de indstillinger, du har sat til False. Tilladte handlinger bidrager med intet til outputtet. Så projektmappen fra det foregående afsnit serialiseres til noget i stil med dette, som kun indeholder de forbudte handlinger plus adgangskode-hashen:

// Konceptuelt output for udsnittet ovenfor (attributter udeladt for kortheds skyld):
// <sheetProtection sheet="1"
//   formatCells="0" formatColumns="0" formatRows="0"
//   insertRows="0" deleteRows="0"
//   password="...4-hex..."/>
// Bemærk, hvad der IKKE er der: intet sort, intet autoFilter, intet selectLockedCells.
// Deres fravær er netop det, der fortæller Excel, at de handlinger forbliver tilladt.

Kommer du fra den gamle hard-codede streng og forventede at se hver attribut stavet ud, ser dette sparsomt ud, næsten forkert. Det er korrekt. En fil, der listede sort="1" og autoFilter="1", ville betyde det samme for en konform læser, men Excel selv skriver den minimale forbud-kun-form, og at matche den holder diffs små og round-trips kedelige. Attributterne objects og scenarios følger den identiske regel: de er som standard tilladt, så de optræder kun som "0", når du forbyder dem, hvilket er det modsatte af det gamle objects="1" scenarios="1", der blev udsendt betingelsesløst

Diagram over de femten HotXLS TXLSXSheetProtectionOption-værdier grupperet i seks familier og slået til og fra gennem den indekserede AllowOption-egenskab og SheetProtectionOptions-sættet i Delphi
De femten tilvalg grupperer sig i seks familier, hver slået til bit for bit eller byttet helheds som et Pascal set

Læs beskyttelsen tilbage: round-trip-fidelitet

En tilladelsesmodel, du kan skrive, men ikke læse, er en envejsdør, og det sædvanlige symptom er en indlæs-redigér-gem-cyklus, der stille udvider tilladelserne. HotXLS lukker det hul. Når ParseWorksheetXml støder på et <sheetProtection>-element, sætter den arket til beskyttet, indfanger adgangskode-hashen, hvis den er til stede, og afkoder derefter hver pr.-handling-attribut tilbage til AllowOption ved brug af den samme konvention omvendt: en attribut, der er til stede og lig med "0", forbyder handlingen; en manglende attribut lader indstillingen stå ved sin tilladte standard

var
  wb: TXLSXWorkbook;
  sh: TXLSXWorksheet;
begin
  wb := TXLSXWorkbook.Create;
  try
    wb.Open('protection.xlsx');
    sh := wb.Sheets[1];                  // XLSX-ark er 1-baserede
    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;

Indlæs filen, writeren producerede, og du får Sort og AutoFilter tilbage som True, FormatCells som False — det sæt, du gemte, intakt. Den symmetri er hele pointen: rediger én celle i et beskyttet, delvist tilladt ark og gem igen, og de fjorten tilladelser, du ikke rørte, overlever i stedet for at falde tilbage til den gamle alt-eller-intet-standard

Diagram over HotXLS XLSX sheetProtection-kodningsreglen i Delphi, hvor en udeladt attribut tillader en handling, og attr=0 forbyder den, og kontrasterer den gamle hardkodede linje med det minimale forbid-only-writer-output
En tilladt handling bidrager slet ingen attribut; kun forbudte handlinger optræder som attr=0

Praktiske noter og begrænsninger

Nogle få ting værd at vide, før du kobler dette ind i en rapporteringspipeline:

  • Adgangskoden er svag med vilje. XLSX-arkbeskyttelse gemmer en 16-bit forældet hash (den samme, Excel har brugt i årtier), bevaret her af hensyn til interoperabilitet. Den afskrækker utilsigtede redigeringer; den modstår ikke en angriber. Behandl den ikke som en hemmelighedsholder. For reel beskyttelse skal du kryptere projektmappen
  • Det er fint at sætte indstillinger, før du beskytter. AllowOption kan tildeles, uanset om arket i øjeblikket er beskyttet eller ej; skifterne beskriver blot, hvad beskyttelsen vil tillade, når Protect er i kraft. UnProtect rydder den beskyttede tilstand og hashen, men lader dit indstillingssæt stå til næste gang
  • Semantikken for låste celler gælder stadig. Beskyttelse blokerer kun redigeringer i celler, hvis Locked-attribut er sat (projektmappens standard). At lade et inputområde være redigerbart er celletypografiens opgave, ikke en beskyttelsesindstilling; de to lag kombineres på samme måde, som de gør i Excel
  • Dette er XLSX-motoren. Indstillingsmodellen spejler XLS-motorens ældre Allow*-egenskaber, men enum- og egenskabsnavnene her (xlsxSpo*, AllowOption) hører til TXLSXWorksheet i lxHandleX. Styrer du også printlayout på de samme ark, dækker gennemgangen af beskyttelse og sideopsætning, hvordan disse indstillinger passer sammen med printområder og sidehoveder, og datavalidering, AutoFilter og tabeller passer naturligt sammen med at lade xlsxSpoAutoFilter stå åben på en låst rapport

Den finkornede beskyttelsesmodel og resten af XLSX-læse-/skrivemotoren følger med i HotXLS-Delphi-komponenten til Delphi og C++Builder; produktsiden indeholder den fulde regneark-API, inklusive den komplette beskyttelsesindstillings-reference