Techninis straipsnis

HotXLS BIFF8 Selection įrašai ir pane slinkimas Delphi

HotXLS darbalapių žymėjimus ir slinkimo pozicijas saugo kiekvienam pane atskirai per vieną API, veikiančią tiek TXLSWorksheet, tiek TXLSXWorksheet: SelectAreas, GetSelectedAreas, ScrollWindow ir TryGetWindowScroll. Klasikiniams .xls failams HotXLS rašo BIFF8 Selection įrašus (0x001D), kurių kiekvienas talpina ne daugiau kaip 1369 sritis, loginius pane vardus paverčia formato apibrėžtais pane baitais, o kiekvieną slinkimo ašį palieka ten, kur jos tikisi Excel — Window2 arba Pane įraše

Problema paprastai išlenda suderinimo ar audito įrankyje. Įrankis atidaro registru eksportą, suranda kiekvieną ląstelę, nesutampančią su šaltinio sistema, ir išsaugo darbaknygę su tomis ląstelėmis, jau pažymėtomis po užšaldyta antraščių eilute, kad peržiūrėtojas pakliūtų į skirtumus, o ne jų ieškotų slinkdamas. Su keturiasdešimt skirtumų viskas gerai. Mėnesio pabaigos faile jų 3 000, o vieno Selection įrašo, laikančio 3 000 sričių, būti negali: jo kūnui reikėtų 18 009 baitų, daugiau nei dvigubai daugiau, kiek vienas BIFF8 įrašas gali nešti. Slinkimo pozicijoje slypi panašūs spąstai. Lange su užšaldytais pane „kur žiūrėjo naudotojas“ yra keturi pane, dalijantys dvi eilučių ir dvi stulpelių pozicijas, o ne viena koordinatė

Kodėl didelė žymė neišsiverčia vienu Selection įrašu?

Didelė žymė reikalauja kelių įrašų, nes BIFF8 įrašo kūnas ribojamas 8224 baitų, o kiekviena pažymėta sritis kainuoja fiksuotus šešis baitus. [MS-XLS] §2.4.248 išdėsto Selection įrašą kaip 9 baitų fiksuotą dalį (pane baitas, rwAct ir colAct aktyviai ląstelei, irefAct aktyviai sričiai ir cref sričių skaičiui), po kurios eina cref RefU struktūrų, kiekviena su dviem 16 bitų eilutėmis ir dviem 8 bitų stulpeliais. Didžiausias telpantis skaičius yra (8224 − 9) / 6, suapvalinta žemyn, tai 1369, ir duoda 8223 baitų kūną — baitu mažiau už ribą. TXLSWorksheet.StoreSelectionGroup tą konstantą naudoja kaip MaxAreasPerRecord ir didesnę grupę rašo iš eilės einančiais to paties pane Selection įrašais, po 1369 sritis

Geriausiai įkanda ta smulkmena, vadinama irefAct. Kiekviena dalis kartoja tą pačią aktyvią eilutę, aktyvų stulpelį ir aktyvios srities indeksą, o irefAct indeksuoja agreguotą visų dalių seką, o ne sritis tame įraše, kuris jį neša. Žymė, viena sritimi viršijanti ribą, tai padaro konkrečiu: 1370 sričių su paskutine aktyvia tampa dviem įrašais, pirmas su cref 1369, antras su cref 1, ir abu neša irefAct 1369. Ta reikšmė didesnė už pačio antrojo įrašo sričių skaičių. Skaitytuvas, tikrinantis irefAct prieš cref kiekviename įraše, atmeta teisėtą failą, o skaitytuvas, pakeičiantis būseną kiekvienu įrašu, praranda pirmąsias 1369 sritis. HotXLS skaitytuvas iš eilės einančius to paties pane įrašus prijungia prie vienos grupės, reikalauja, kad kiekviena dalis sutartų dėl aktyvios ląstelės ir indekso, o intervalo patikrą paleidžia tik ties darbalapio EOF įrašu, kai visa seka jau žinoma. Tad pane-first SelectAreas perkrova neturi 1369 sričių lubų. Ji patvirtina kiekvieną A1 nuorodą ir aktyvųjį indeksą dar prieš imdama darbalapio rašymo užraktą, o sugedus bet kokiai daliai grąžina False nepakeitusi ankstesnės žymės

Kodėl HotXLS didelę darbalapio žymę rašo keliais BIFF8 Selection įrašais: 8 224 baitų kūno riba telpa 9 fiksuoti baitai ir 1369 šešių baitų RefU sritys, tad 3 000 sričių tampa trimis to paties pane įrašais po 1369, 1369 ir 262, o irefAct indeksuoja agreguotą seką, tad 1370 sričių su paskutine aktyvia abiem įrašams duoda irefAct 1369
Kiekviena dalis kartoja tą pačią aktyvią ląstelę ir indeksą, HotXLS skaitytuvas iš eilės einančius to paties pane įrašus prijungia prie vienos grupės, o intervalo patikra paleidžiama tik ties EOF įrašu, kai visa seka žinoma
var
  Book: TXLSWorkbook;
  Sheet: TXLSWorksheet;
  Diffs: TXLSSelectedAreas;
  I: Integer;
begin
  Book := TXLSWorkbook.Create;
  try
    Sheet := Book.Sheets.Add;
    Sheet.FreezePanes(1, 1);           // antraščių eilutė ir A stulpelis lieka vietoje

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

    // Užšaldymas perstato išsaugotą žymę, tad žymėkite po užšaldymo
    // 3000 sričių išsaugomos kaip trys Selection įrašai: 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;

Kurį pane baitą naudoja Selection įrašas?

Selection įrašas savo pane atpažįsta pagal numerinį kodą, kurį apibrėžia formatas: 0 — apatinei dešinei, 1 — viršutinei dešinei, 2 — apatinei kairei, 3 — viršutinei kairei. Viešasis enumas TXLSPanePosition deklaruotas skaitymo tvarka — xlspTopLeft, xlspTopRight, xlspBottomLeft, xlspBottomRight — tad Ord(xlspTopLeft) yra 0, o tai faile yra apatinis dešinysis pane. Enumą įmetus tiesiai į pane baitą, kiekviena viršutinė kairė žymė būtų įrašyta į apatinį dešinįjį pane be jokios klaidos. Kiekvienas pane atpažįstantis HotXLS įėjimo taškas enumą konvertuoja per aiškų case sakinį, tad kvietėjai su numeriniais kodais apskritai nesiduria. Tikrinamas ir pane egzistavimas: viršutinis dešinysis pane egzistuoja tik su vertikaliu skaidymu, apatinis kairysis — tik su horizontaliu, o apatinis dešinysis — tik su abiem. Pane, kurio dabartinė skaidymo ar užšaldymo geometrija neturi, atveju SelectAreas grąžina False, o GetSelectedAreas — tuščią masyvą su ActiveAreaIndex lygiu -1, nesukurdama nei pane, nei žymės objekto, nei ląstelės darbaknygėje

Kaip HotXLS TXLSPanePosition atvaizduoja į BIFF8 Selection pane baitą: enumas deklaruotas skaitymo tvarka, tad Ord(xlspTopLeft) yra 0, o failas apibrėžia 0 apatinei dešinei, 1 viršutinei dešinei, 2 apatinei kairei ir 3 viršutinei kairei, tad kiekvienas pane atpažįstantis įėjimo taškas konvertuoja per aiškų case sakinį
Enumą įmetus tiesiai į pane baitą, kiekviena viršutinė kairė žymė atsidurtų apatiniame dešiniajame pane, tad HotXLS prieš rašydamas dar patikrina, ar pane egzistuoja dabartinėje skaidymo ar užšaldymo geometrijoje

Kur gyvena kiekvieno pane slinkimo pozicija?

Kiekvieno pane slinkimo pozicija padalinta tarp dviejų įrašų, nes keturi pane dalijasi vos dviem eilučių ir dviem stulpelių pozicijomis. Klasikinėje darbaknygėje viršutinių pane pirmoji matoma eilutė ir kairiųjų pane pirmasis matomas stulpelis yra Window2.rwTop ir Window2.colLeft, o apatinių pane eilutė ir dešiniųjų pane stulpelis — Pane.rwTop ir Pane.colLeft. Tad ScrollWindow(xlspTopRight, R, C) rašo Window2.rwTop ir Pane.colLeft, ir nustačius viršutinio dešiniojo pane stulpelį pajuda ir apatinis dešinysis pane, lygiai kaip Excel abu jungia viena horizontalia slinkimo juosta. Viešieji metodai naudoja eilučių ir stulpelių numerius nuo 1. Trūkstamas pane grąžina False ir abu užklausos išėjimus nustato į nulį, o už ribų išėjusi koordinatė atmetama, dar nepakeitus nė vienos ašies. Čia niekas nepriklauso nuo to, kaip žiūryklė piešia tinklelį. Atvaizdavimo kontrolė turi savo TopRow ir LeftCol, kaip aprašo straipsnis apie darbaknygių atvaizdavimą savame VCL tinklelyje, ir tai vykdymo metu būsena, o ne tai, kas išsaugoma

Kur gyvena kiekviena HotXLS pane slinkimo ašis: keturi pane dalijasi dviem eilučių ir dviem stulpelių pozicijomis, tad viršutinė eilutė ir kairysis stulpelis yra Window2.rwTop ir Window2.colLeft, o apatinė eilutė ir dešinysis stulpelis — Pane.rwTop ir Pane.colLeft, ir ScrollWindow(xlspTopRight, 1, 6) rašo po vieną Window2 ir po vieną Pane lauką, tad apatinis dešinysis seka paskui
XLSX tuos pačius duomenis išskleidžia per sheetView ir pane topLeftCell atributus, ir abiejų sluoksnių suskleidimas į vieną yra būtent tas būdas, kuriuo viršutinė ar kairė slinkimo pozicija tyliai dingsta įkeliant

XLSX tuos pačius duomenis išskleidžia per du elementus: sheetView/@topLeftCell (ECMA-376 1 dalis, §18.3.1.87) visam langui ir vaiko pane/@topLeftCell (§18.3.1.66) skaidymo apatinei dešiniajai pusei. Abu atributai gali būti vienu metu. HotXLS pirmiausia išorinį atributą perskaito į lango lygio laukus, leidžia pane vaikui perrašyti tik pane lygio laukus ir abu atgal rašo atskirai. Abu sluoksnius suskleidus į vieną viršutinė ar kairė slinkimo pozicija būtent taip ir dingsta tyliai įkeliant. Darbalapių kopijos neša abu sluoksnius abiejuose varikliuose. Senesnieji įėjimo taškai išlaiko savo pradinį elgesį: klasikinės ScrollRow ir ScrollColumn savybės ir nuo nulio skaičiuojantys XLSX SetPaneScroll bei GetPaneScroll. Pati užšaldymo ir skaidymo geometrija konfigūruojama lapo lygio nustatymais, aptartais lapo apsaugos, puslapio sąrankos ir spausdinimo straipsnyje

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

  // Apatinis dešinysis: apatinė eilučių ašis (Pane.rwTop) ir dešinioji stulpelių ašis (Pane.colLeft)
  Sheet.ScrollWindow(xlspBottomRight, 500, 3);

  // Viršutinis dešinysis dalijasi dešiniąja stulpelių ašimi, tad tai pajudina ir apatinį dešinįjį į 6 stulpelį
  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]));
    // Apatinis dešinysis prasideda 500 eilutėje, 6 stulpelyje
end;

Kas nutinka, kai Selection įrašas sugadintas?

Kai Selection įrašas sugadintas, HotXLS jį laiko neperšviečiamais baitais, praneša diagnostikos kodą 1304 (xlsDiagnosticSelectionRecordInvalid) ir įrašymo metu originalų kūną grąžina baitas į baitą. Dar įrašui prisijungiant prie savo pane grupės, skaitytuvas jį tikrina tvarka. Pane baitas turi būti 3 arba mažiau. Vieno pane įrašai sraute turi eiti iš eilės. Turi būti visi 9 fiksuoti baitai. cref turi būti tarp 1 ir 1369, o kūnas turi būti lygiai 9 + cref × 6 baitų ilgio. Kiekviena grupės dalis turi sutarti dėl aktyvios ląstelės ir irefAct, irefAct ženklelio bitas negali būti nustatytas, aktyvusis stulpelis turi būti tinklelyje, ir jokia sritis negali turėti apsukotų ribų. Vieno fizinio įrašo problemos pranešamos po kartą įrašui. Prieštaravimai, matomi tik agregavus, pavyzdžiui irefAct, rodantis už viso sričių skaičiaus, arba aktyvi ląstelė už indeksuotos srities, pranešami po kartą grupei ties EOF. Netinkama grupė lieka nepastebima tipizuotai API: GetSelectedAreas tam pane grąžina tuščią masyvą su indeksu -1, o visi kiti pane dirba toliau

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;

Kaip žymės išgyvena eilučių ir stulpelių įterpimus?

Žymės išgyvena struktūrinius redagavimus, nes visų eilučių ar stulpelių įterpimas ar trynimas per vieną bendrą remapper perskirsto kiekvieną atvaizduotą pane grupę ir klasikiniame, ir XLSX varikliuose. Išgyvenusios sritys išlaiko savo tvarką, o aktyvioji sritis — savo tapatybę. Jei aktyvioji sritis ištrinta, aktyviu tampa pirmasis išgyvenęs įpėdinis, o jei nieko neseka — paskutinis išgyvenęs pirmtakas. Jei ištrintos visos sritys, grupė susiskleidžia į vieną ląstelę ties trynimo riba, o aktyvioji ląstelė, nebekrentanti į pasirinktą sritį, perkelia į tos srities viršutinį kairįjį kampą, tad indeksas ir koordinatė niekada nesipriešina vieni kitiems. Ribos yra sąmoningos. Netinkamas klasikines grupes perskirstytojas praleidžia, užuot perrašęs jas į išgalvotą žymę, tad jų originalūs baitai vis tiek atkeliauja pirmyn ir atgal. Vieno pane redagavimas pakeičia tik to pane įrašus ir palieka kitus baitas į baitą identiškus. ODS apskritai negauna jokios pane žymės būsenos, nes ODF neturi atitinkamos darbalapio vaizdo struktūros, kurioje ją nešioti

Jei jūsų programa rašo .xls failus, kuriuos naudotojai atidaro ir turi naršyti — ar peržiūrėti pažymėtas ląsteles, ar tęsti nuo tos vietos, kur stojo, ar dalintis užšaldytu valdymo skydu — pane atpažįstanti žymių ir slinkimo API yra HotXLS Delphi spreadsheet komponento dalis ir veikia taip pat XLS bei XLSX