Teknisk artikel

XLSX-kalkylbladsskydd i Delphi: 15 tillåtna alternativ

Du lämnar en färdig arbetsbok till en kollega och ber dem filtrera den, inte skriva om den. Så du skyddar kalkylbladet. I äldre HotXLS-byggar skrev den gesten bara en sak till filen: <sheetProtection sheet="1" objects="1" scenarios="1"/>, hårdkodat, varje gång. Kalkylbladet låstes, lösenordshashen lades till och användaren kunde inte göra någonting alls, inte ens den sortering och filtrering du faktiskt ville lämna öppen. Excels egen dialog "Skydda blad" har femton kryssrutor av just den här anledningen, och motorn kunde inte uttrycka någon av dem. Det gapet är vad skyddsmodellen i v2.91.0 stänger

HotXLS är en inbyggd VCL-komponent för kalkylblad i Delphi och C++Builder som läser och skriver XLS och XLSX utan att Excel är installerat. Den här artikeln handlar om XLSX-delen av kalkylbladsskydd: den nya TXLSXSheetProtectionOption enum, den AllowOption egenskapen som växlar varje behörighet, och den enda OOXML-kodningsregel som alltid ställer till det för den som skriver ett <sheetProtection> element för hand

Vad kalkylbladsskydd faktiskt skyddar

Först gränsen, eftersom den avgör hur mycket du ska lita på allt detta. Kalkylbladsskydd i OOXML-formatet för kalkylblad (ECMA-376) är en interaktionspolicy, inte kryptering. Det talar om för en kompatibel applikation vilka ändringar den ska vägra medan bladet är skyddat. Cellvärdena ligger fortfarande i xl/worksheets/sheetN.xml som ren text; packa upp .xlsx så finns de där. Det valfria lösenordet lagras som en kort äldre hash, inte som en nyckel som krypterar något. Den som döper om filen, öppnar delen och tar bort raden <sheetProtection> kan läsa och redigera allt

Så skyddet svarar "stoppa min kollega från att råka förstöra en formel", inte "hålla dessa data hemliga för någon som menar allvar". Det är olika problem med olika verktyg. Om du behöver konfidentialitet vill du ha arbetsboksnivåns kryptering som täcks i AES-skyddad XLSX-utdata, som faktiskt krypterar paketet. Kalkylbladsskydd och arbetsbokskryptering fungerar fint tillsammans, men bara den andra är ett lås. Håll den gränsen tydlig så är resten av sidan bara rördragning

De femton alternativen och egenskapen AllowOption

Varje kalkylblad bär nu en uppsättning TXLSXSheetProtectionOption som beskriver vad användaren fortfarande får göra medan bladet är skyddat. Medlemmarna mappar ett till ett mot OOXML-attributen och mot kryssrutorna i Excels dialog:

  • xlsxSpoEditObjects, xlsxSpoEditScenarios - redigera ritobjekt och what-if-scenarier
  • xlsxSpoFormatCells, xlsxSpoFormatColumns, xlsxSpoFormatRows - formatera om celler, kolumner och rader
  • xlsxSpoInsertColumns, xlsxSpoInsertRows, xlsxSpoInsertHyperlinks - infoga kolumner, rader och länkar
  • xlsxSpoDeleteColumns, xlsxSpoDeleteRows - ta bort kolumner och rader
  • xlsxSpoSelectLockedCells, xlsxSpoSelectUnlockedCells - flytta markeringen till låsta eller olåsta celler
  • xlsxSpoSort, xlsxSpoAutoFilter, xlsxSpoPivotTables - sortera områden, använda AutoFilter-rullgardiner och arbeta med PivotTables

Du läser och skriver enskilda bitar via den indexerade AllowOption egenskapen på TXLSXWorksheet. AllowOption[Opt] = True innebär att åtgärden är tillåten; att sätta den False förbjuder den. Hela uppsättningen går också att nå på en gång via SheetProtectionOptions, en TXLSXSheetProtectionOptions (en vanlig Pascal set of), så att du kan spara den, återställa den eller ersätta den i ett svep

Standardvärdet är viktigt och avsiktligt: ett nyss skapat kalkylblad börjar med varje alternativ tillåtet. Konstruktorn sätter SheetProtectionOptions med hela spannet, [Low(TXLSXSheetProtectionOption)..High(TXLSXSheetProtectionOption)]. Därifrån snävar du in genom att utesluta de åtgärder du vill förbjuda, i stället för att bygga upp en behörighetsmängd från noll. Det valet är det som får skrivarens kodningsregel nedan att stämma med Excels beteende

Skydda ett blad men lämna sortering och filtrering öppna

Här är det vanliga fallet från början till slut: skydda en färdig rapport så att layouten inte kan omformas, men låt läsaren sortera och filtrera den. Lägg märke till att Protect och alternativen är oberoende. Protect växlar bladet till skyddat läge och lagrar den valfria lösenordshashen; den rör inte alternativmängden. Du justerar AllowOption separat, och växlarna börjar gälla när bladet är skyddat och sparat

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;

Två saker att läsa ut ur den kodsnutten. De Sort och AutoFilter raderna skrivs ut explicit även om båda har standardvärdet True; det är dokumentation för nästa underhållare, inte ett funktionellt krav. Och eftersom standardvärdena är tillåtande är de enda rader som ändrar utdatafilen de som sätter ett alternativ till False. Det är inte ett misstag i det här API:t, utan OOXML-wireformatet som lyser igenom, vilket är nästa avsnitt

Kodningsregeln: utelämnat betyder tillåtet, attr=0 betyder förbjudet

Det här är den enda motintuitiva detaljen i hela funktionen, och det är här handskrivna <sheetProtection> vanligtvis går fel. I OOXML är varje per-åtgärdsattribut en förbudsflagga och dess frånvaro betyder tillåtelse. Ett attribut som saknas betyder att åtgärden är tillåten. Ett attribut skrivet som "0" betyder att åtgärden är förbjuden medan bladet är skyddat. Det finns ingen formatCells="1" i en välformad fil för att betyda "formatering är tillåten"; du lämnar helt enkelt attributet ute. (Standardvärdet för ett utelämnat attribut är OOXML-booles standardvärde true, och dessa attribut är namngivna så att "true" betyder att motsvarande redigering är tillåten.)

HotXLS-skrivaren speglar det exakt. Den skriver ut sheet="1" för att slå på skyddet, sedan går den igenom alternativmängden och skriver attr="0" bara för de alternativ du sätter till False. Tillåtna åtgärder bidrar inte med någonting till utdata. Så arbetsboken från föregående avsnitt serialiseras ungefär så här, och bär bara de förbjudna åtgärderna plus lösenordshashen:

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

Om du kom från den gamla hårdkodade strängen och förväntade dig att se varje attribut utskrivet känns detta sparsmakat, nästan fel. Det är korrekt. En fil som listade sort="1" och autoFilter="1" skulle betyda samma sak för en kompatibel läsare, men Excel själv skriver den minimala formen med bara förbjudna attribut, och att matcha den håller diffarna små och roundtrips tråkiga. Attributen objects och scenarios följer samma regel: de är tillåtna som standard, så de visas bara som "0" när du förbjuder dem, vilket är motsatsen till de gamla objects="1" scenarios="1" som skrevs ut utan villkor

Att läsa tillbaka skydd: roundtrip-trogenhet

En behörighetsmodell som du kan skriva men inte läsa är en enkelriktad dörr, och det vanliga symtomet är en load-edit-save-loop som i tysthet breddar behörigheterna. HotXLS stänger den dörren. När ParseWorksheetXml träffar ett <sheetProtection> element sätter den bladet som skyddat, fångar lösenordshashen om den finns och avkodar sedan varje per-åtgärdsattribut tillbaka till AllowOption med samma konvention i omvänd riktning: ett attribut som finns och är lika med "0" förbjuder åtgärden; ett attribut som saknas lämnar alternativet i dess tillåtna standardläge

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;

Läser du in filen som skrivaren skapade får du Sort och AutoFilter tillbaka som True, FormatCells som False - mängden du sparade, intakt. Den symmetrin är hela poängen: redigera en cell i ett skyddat, delvis tillåtet blad och spara igen, så överlever de fjorton behörigheter du inte rörde i stället för att falla tillbaka till den gamla allt-eller-inget-standardinställningen

Praktiska noteringar och begränsningar

Några saker värda att känna till innan du kopplar in detta i en rapportpipeline:

  • Lösenordet är svagt med avsikt. XLSX-kalkylbladsskydd lagrar en 16-bitars äldre hash (samma som Excel har använt i årtionden), här för kompatibilitetens skull. Det avskräcker från oavsiktliga ändringar, det står inte emot en angripare. Behandla det inte som en hemlighetsvakt. För verkligt skydd, kryptera arbetsboken
  • Att sätta alternativ innan du skyddar är okej. AllowOption kan tilldelas oavsett om bladet för närvarande är skyddat eller inte; växlarna beskriver helt enkelt vad skyddet kommer att tillåta när Protect är i kraft. UnProtect tar bort skyddstillståndet och hashvärdet men lämnar din alternativmängd kvar till nästa gång
  • Semantiken för låsta celler gäller fortfarande. Skydd blockerar bara redigering av celler vars Locked attribut är satt (arbetsbokens standard). Att låta ett inmatningsområde vara redigerbart är cellstilens jobb, inte ett skyddsalternativ; de två lagren samverkar på samma sätt som i Excel
  • Det här är XLSX-motorn. Alternativmodellen speglar den äldre XLS-motorns Allow* egenskaper, men enum- och egenskapsnamnen här (xlsxSpo*, AllowOption) hör till TXLSXWorksheet i lxHandleX. Om du också styr utskriftslayouten på samma blad, så täcker genomgången av skydd och sidinställningar hur dessa inställningar ligger bredvid utskriftsområden och sidhuvuden, och datavalidering, AutoFilter och tabeller passar naturligt ihop med att lämna xlsxSpoAutoFilter öppet på en låst rapport

Den finmaskiga skyddsmodellen och resten av XLSX-läs- och skrivmotorn följer med i HotXLS Component för Delphi och C++Builder; produktsidan innehåller hela kalkylblads-API:t inklusive den fullständiga referensen för skyddsalternativ