Teknisk artikel

BIFF8 Selection-poster och fönsterrutornas rullning i HotXLS

HotXLS lagrar kalkylbladsurval och rullningspositioner per fönsterruta genom ett gemensamt fönsterrute-API på både TXLSWorksheet och TXLSXWorksheet: SelectAreas, GetSelectedAreas, ScrollWindow och TryGetWindowScroll. För klassiska .xls-filer skriver HotXLS BIFF8 Selection-poster (0x001D) med högst 1369 områden var, översätter logiska fönsterrutenamn till de pane-byte formatet definierar, och lägger varje rullningsaxel på den Window2- eller Pane-post där Excel förväntar sig den

Problemet dyker oftast upp i ett avstämnings- eller granskningsverktyg. Verktyget öppnar en redovisningsexport, hittar varje cell som inte stämmer med källsystemet, och sparar arbetsboken med de cellerna redan markerade under en frusen rubrikrad, så att granskaren landar på avvikelserna i stället för att leta upp dem med rullningen. Med fyrtio avvikelser fungerar det fint. Månadsslutfilen har 3 000, och en enda Selection-post med 3 000 områden kan inte existera: dess kropp skulle behöva 18 009 byte, mer än dubbelt så mycket som en BIFF8-post kan bära. Rullningspositionen har en liknande fälla. På ett blad med frusna rutor är "där användaren tittade" fyra fönsterrutor som delar två radpositioner och två kolumnpositioner, inte en enda koordinat

Varför behöver ett stort urval mer än en Selection-post?

Ett stort urval behöver flera poster för att en BIFF8-postkropp är taklagd vid 8224 byte och varje markerat område kostar fasta sex byte. [MS-XLS] §2.4.248 beskriver Selection-posten som en fast del på 9 byte (pane-byten, rwAct och colAct för den aktiva cellen, irefAct för det aktiva området och cref för områdesantalet) följd av cref RefU-strukturer, var och en med två 16-bitarsrader och två 8-bitarskolumner. Största antal som ryms är (8224 − 9) / 6 avrundat nedåt, vilket är 1369, och det ger en kropp på 8223 byte, en byte under gränsen. TXLSWorksheet.StoreSelectionGroup använder den konstanten som MaxAreasPerRecord och skriver en större grupp som på varandra följande Selection-poster för samma fönsterruta, 1369 områden i taget

Detaljen som biter är irefAct. Varje bit upprepar samma aktiva rad, aktiva kolumn och aktiva områdesindex, och irefAct indexerar den aggregerade sekvensen av alla bitar, inte områdena inuti posten som bär den. Ett urval ett område över gränsen gör det konkret: 1370 områden med det sista aktiva blir två poster, den första med cref 1369 och den andra med cref 1, och båda bär irefAct 1369. Det värdet är större än den andra postens eget områdesantal. En läsare som kontrollerar irefAct mot cref i varje post avvisar en giltig fil, och en läsare som ersätter sitt tillstånd vid varje post tappar de första 1369 områdena. HotXLS-läsaren samlar på varandra följande poster för samma fönsterruta i en gemensam grupp, kräver att varje bit är överens om den aktiva cellen och indexet, och kör intervallkontrollen först vid bladets EOF-post, när hela sekvensen är känd. Fönsterruteversionen av SelectAreas-överlagringen har därför inget tak på 1369 områden. Den validerar varje A1-referens och det aktiva indexet innan den tar skrivlåset för kalkylbladet, och den returnerar False med det tidigare urvalet orört om något är felformat

Varför HotXLS skriver ett stort kalkylbladsurval som flera BIFF8 Selection-poster: kroppstaket på 8 224 byte rymmer 9 fasta byte plus 1369 RefU-områden på sex byte, så 3 000 områden blir tre poster för samma fönsterruta med 1369, 1369 och 262, och irefAct indexerar den aggregerade sekvensen, så 1370 områden med det sista aktiva ger båda posterna irefAct 1369
Varje bit upprepar samma aktiva cell och index, HotXLS-läsaren samlar på varandra följande poster för samma fönsterruta i en grupp, och intervallkontrollen körs först vid EOF-posten när hela sekvensen är känd
var
  Book: TXLSWorkbook;
  Sheet: TXLSWorksheet;
  Diffs: TXLSSelectedAreas;
  I: Integer;
begin
  Book := TXLSWorkbook.Create;
  try
    Sheet := Book.Sheets.Add;
    Sheet.FreezePanes(1, 1);           // rubrikraden och kolumn A stannar på plats

    SetLength(Diffs, 3000);
    for I := 0 to High(Diffs) do
      Diffs[I] := Format('C%d', [I + 2]);

    // Frysning nollställer det lagrade urvalet, så markera efter frysningen.
    // 3000 områden sparas som tre Selection-poster: 1369 + 1369 + 262
    if not Sheet.SelectAreas(xlspBottomRight, Diffs, 0) then
      raise Exception.Create('Selection rejected');

    Book.SaveAs('reconciliation.xls');
  finally
    Book.Free;
  end;
end;

Vilket pane-byte använder en Selection-post?

En Selection-post identifierar sin fönsterruta med den numeriska kod formatet definierar: 0 för nedre högra, 1 för övre högra, 2 för nedre vänstra och 3 för övre vänstra. Den publika enumerationen TXLSPanePosition är deklarerad i läsordning, xlspTopLeft, xlspTopRight, xlspBottomLeft, xlspBottomRight, så Ord(xlspTopLeft) är 0, vilket är den nedre högra fönsterrutan i filen. En rak typkonvertering av enumen till pane-byten skulle skriva varje övre vänstra urval till den nedre högra rutan utan något fel. Varje fönsterrutekänslig ingångspunkt i HotXLS konverterar enumen genom en explicit case-sats i stället, så anropare behöver aldrig bry sig om de numeriska koderna. Fönsterrutans existens kontrolleras också: den övre högra rutan finns bara med en vertikal delning, den nedre vänstra bara med en horisontell, och den nedre högra bara med båda. För en fönsterruta som den aktuella delnings- eller frysningsgeometrin inte har returnerar SelectAreas False, och GetSelectedAreas returnerar en tom array med ActiveAreaIndex satt till -1, utan att skapa någon fönsterruta, något urvalsobjekt eller någon cell i arbetsboken

Hur HotXLS mappar TXLSPanePosition på BIFF8 Selection-pane-byten: enumen är deklarerad i läsordning så Ord(xlspTopLeft) är 0, medan filen definierar 0 för nedre högra, 1 för övre högra, 2 för nedre vänstra och 3 för övre vänstra, så varje fönsterrutekänslig ingångspunkt konverterar genom en explicit case-sats
En rak typkonvertering till pane-byten skulle skriva varje övre vänstra urval till den nedre högra rutan, så HotXLS kontrollerar också rutans existens mot den aktuella delnings- eller frysningsgeometrin innan den skriver

Var ligger varje fönsterrutas rullningsposition?

Varje fönsterrutas rullningsposition är delad på två poster, för fyra fönsterrutor delar bara två radpositioner och två kolumnpositioner. I en klassisk arbetsbok är de övre rutornas första synliga rad och de vänstra rutornas första synliga kolumn Window2.rwTop och Window2.colLeft, medan de nedre rutornas rad och de högra rutornas kolumn är Pane.rwTop och Pane.colLeft. ScrollWindow(xlspTopRight, R, C) skriver alltså Window2.rwTop och Pane.colLeft, och sätter man kolumnen för den övre högra rutan flyttas den nedre högra med, precis som de två delar en horisontell rullningslist i Excel. De publika metoderna använder rad- och kolumnnummer med bas ett. En saknad fönsterruta returnerar False och sätter båda frågeutgångarna till noll, och en koordinat utanför intervallet avvisas innan någon axel ändras. Ingenting här beror på hur en visare ritar rutnätet. En renderingskontroll håller sina egna TopRow och LeftCol, som artikeln om att rendera arbetsböcker i ett eget VCL-rutnät beskriver, och det är körtidstillstånd, inte det som sparas

Var varje HotXLS-rullningsaxel bor: fyra fönsterrutor delar två rad- och två kolumnpositioner, så den övre raden och vänstra kolumnen är Window2.rwTop och Window2.colLeft medan den nedre raden och högra kolumnen är Pane.rwTop och Pane.colLeft, och ScrollWindow(xlspTopRight, 1, 6) skriver ett Window2-fält plus ett Pane-fält så att nedre högra följer med
XLSX sprider samma data över attributen sheetView och pane topLeftCell, och att kollapsa de två lagren till ett är exakt hur en övre eller vänster rullningsposition tyst försvinner vid inläsning

XLSX sprider samma data över två element: sheetView/@topLeftCell (ECMA-376 Part 1, §18.3.1.87) för fönstret som helhet och barnet pane/@topLeftCell (§18.3.1.66) för den nedre högra sidan av en delning. Båda attributen kan finnas samtidigt. HotXLS läser det yttre attributet först till fönsternivåfälten, låter pane-barnet åsidosätta bara fälten på fönsterrutenivå, och skriver tillbaka båda separat. Att kollapsa de två lagren till ett är exakt hur en övre eller vänster rullningsposition tyst försvinner vid inläsning. Bladkopior bär båda lagren i båda motorerna. De äldre ingångspunkterna behåller sitt ursprungliga beteende: de klassiska egenskaperna ScrollRow och ScrollColumn, samt de nollbaserade XLSX-metoderna SetPaneScroll och GetPaneScroll. Frysnings- och delningsgeometrin i sig konfigureras med bladnivåinställningarna som bladskydd, sidinställning och utskrift tar upp

var
  Row, Col: Integer;
begin
  Sheet.FreezePanes(1, 1);

  // Nedre högra: nedre radaxeln (Pane.rwTop) och högra kolumnaxeln (Pane.colLeft)
  Sheet.ScrollWindow(xlspBottomRight, 500, 3);

  // Övre högra delar högra kolumnaxeln, så detta flyttar också nedre högra till kolumn 6
  Sheet.ScrollWindow(xlspTopRight, 1, 6);

  if Sheet.TryGetWindowScroll(xlspBottomRight, Row, Col) then
    Memo1.Lines.Add(Format('Bottom-right starts at row %d, column %d', [Row, Col]));
    // Nedre högra börjar på rad 500, kolumn 6
end;

Vad händer när en Selection-post är korrupt?

När en Selection-post är korrupt behåller HotXLS den som opaka byte, rapporterar diagnostikkod 1304 (xlsDiagnosticSelectionRecordInvalid) och skriver tillbaka den ursprungliga kroppen byte för byte vid sparning. Innan en post ansluter till sin fönsterrutas grupp kontrollerar läsaren den i ordning. Pane-byten måste vara 3 eller mindre. Poster för en och samma fönsterruta måste ligga sammanhängande i strömmen. De 9 fasta bytena måste finnas. cref måste ligga mellan 1 och 1369, och kroppen måste vara exakt 9 + cref × 6 byte lång. Varje bit i en grupp måste vara överens om den aktiva cellen och irefAct, irefAct får inte ha sin teckenbit satt, den aktiva kolumnen måste ligga på rutnätet, och inget område får ha omvända gränser. Problem i en enstaka fysisk post rapporteras en gång per post. Motsägelser som bara dyker upp efter aggregering, som irefAct som pekar förbi det totala områdesantalet eller en aktiv cell utanför det indexerade området, rapporteras en gång per grupp vid EOF. En ogiltig grupp förblir osynlig för det typade API:et: GetSelectedAreas returnerar en tom array med index -1 för den fönsterrutan, medan alla andra rutor fortsätter fungera

var
  I: Integer;
  D: TXLSDiagnostic;
begin
  if Book.Open('supplier-upload.xls') <> 1 then
    Exit;
  for I := 0 to Book.Diagnostics.Count - 1 do
  begin
    D := Book.Diagnostics[I];
    if D.Code = xlsDiagnosticSelectionRecordInvalid then
      Log.Add(Format('%s: record $%.4x kept opaque (%s)',
        [D.SheetName, D.RecordId, D.Message]));
  end;
end;

Hur överlever urval rad- och kolumninsättningar?

Urval överlever strukturella redigeringar för att infogning eller borttagning av hela rader eller kolumner mappar om varje representerad fönsterrutegrupp i både Classic- och XLSX-motorn genom en gemensam ommappare. Överlevande områden behåller sin ordning och det aktiva området behåller sin identitet. Raderas det aktiva området blir den första överlevande efterträdaren aktiv, sedan den sista överlevande föregångaren om inget följer den. Raderas alla områden kollapsar gruppen till en cell vid borttagningsgränsen, och en aktiv cell som inte längre ligger inuti det valda området flyttas till områdets övre vänstra hörn, så att index och koordinat aldrig motsäger varandra. Gränserna är medvetna. Ogiltiga klassiska grupper hoppas över av ommapparen i stället för att skrivas om till ett påhittat urval, så deras ursprungliga byte kommer fortfarande ut exakt som de gick in. Redigering av en fönsterruta ersätter bara den rutans poster och lämnar de andra byteidentiska. ODS får inget tillstånd för fönsterruteurval alls, eftersom ODF saknar en motsvarande struktur för kalkylbladsvy att bära det

Skriver din applikation .xls-filer som användare öppnar och behöver navigera i, oavsett om det gäller att granska flaggade celler, fortsätta där de slutade eller dela en frusen dashboard, är urvals- och rullnings-API:et som förstår fönsterrutor en del av HotXLS Delphi-kalkylbladskomponenten, och det fungerar likadant för XLS och XLSX