Tekninen artikkeli

HotXLS:n BIFF8 Selection-tietueet ja pane-vieritys Delphissä

HotXLS tallentaa worksheetien valinnat ja vierityspositiot paneittain yhden pane-tietoisen API:n kautta sekä TXLSWorksheet- että TXLSXWorksheet-luokissa: SelectAreas, GetSelectedAreas, ScrollWindow ja TryGetWindowScroll. Klassisiin .xls-tiedostoihin HotXLS kirjoittaa BIFF8 Selection-tietueita (0x001D), joissa on enintään 1369 aluetta kussakin, kääntää loogiset pane-nimet formaatin määrittelemiksi pane-tavuiksi ja pitää jokaisen vieritysakselin Window2- tai Pane-tietueessa siinä missä Excel sen odottaa

Ongelma ilmenee yleensä täsmäytys- tai auditointityökalussa. Työkalu avaa kirjanpitovientien exportin, etsii jokaisen solun joka on eri mieltä lähdejärjestelmän kanssa ja tallentaa työkirjan niin, että nuo solut ovat jo valittuina jäädytetyn otsikkorivin alla, joten tarkastaja päätyy suoraan eroihin etsimättä niitä vierittämällä. Neljälläkymmenellä erolla se toimii ihan hyvin. Kuukauden lopun tiedostossa niitä on 3 000, eikä yksi Selection-tietue 3 000 alueella voi olla olemassakaan: sen runko veisi 18 009 tavua, yli kaksi kertaa sen mitä yksi BIFF8-tietue voi kantaa. Vierityspositiossa on vastaava ansa. Sheetillä jolla on jäädytetyt panet, ”se mihin käyttäjä katsoi” on neljä panelia jotka jakavat kaksi rivipositiota ja kaksi sarakepositiota, ei yksi koordinaatti

Miksi suuri valinta tarvitsee useamman kuin yhden Selection-tietueen?

Suuri valinta tarvitsee useita tietueita, koska BIFF8-tietueen runko on rajattu 8224 tavuun ja jokainen valittu alue maksaa kiinteät kuusi tavua. [MS-XLS] §2.4.248 jäsentää Selection-tietueen 9-tavuiseksi kiinteäksi osaksi (pane-tavu, rwAct ja colAct aktiiviselle solulle, irefAct aktiiviselle alueelle ja cref alueiden määrälle), jota seuraa cref kappaletta RefU-rakenteita, joissa kussakin on kaksi 16-bittistä riviä ja kaksi 8-bittistä saraketta. Suurin mahtuva määrä on (8224 − 9) / 6 pyöristettynä alaspäin, eli 1369, ja se tuottaa 8223-tavuisen rungon, yhden tavun rajan ali. TXLSWorksheet.StoreSelectionGroup käyttää tuota lukua vakionaan MaxAreasPerRecord ja kirjoittaa suuremman ryhmän peräkkäisinä saman panelin Selection-tietueina, 1369 aluetta kerrallaan

Yksityiskohta joka puree on irefAct. Jokainen erä toistaa saman aktiivisen rivin, aktiivisen sarakkeen ja aktiivisen alueen indeksin, ja irefAct indeksoi kaikkien erien yhdistettyä järjestystä, ei sen tietueen sisäisiä alueita joka sitä kantaa. Rajan yli menevä yhden alueen valinta tekee tästä konkreettista: 1370 aluetta joista viimeinen on aktiivinen muodostaa kaksi tietuetta, ensimmäinen cref 1369 ja toinen cref 1, ja molemmat kantavat arvoa irefAct 1369. Se arvo on suurempi kuin toisen tietueen oma aluemäärä. Lukija joka tarkistaa irefActin crefiin jokaisessa tietueessa hylkää kelvollisen tiedoston, ja lukija joka korvaa tilansa jokaisella tietueella pudottaa ensimmäiset 1369 aluetta. HotXLS:n lukija liittää peräkkäiset saman panelin tietueet yhdeksi ryhmäksi, vaatii jokaisen erän olevan samaa mieltä aktiivisesta solusta ja indeksistä, ja ajaa aluetarkistuksen vasta worksheetin EOF-tietueessa kun koko järjestys on tiedossa. Pane ensin -tyyppisellä SelectAreas-overloadilla ei siksi ole 1369 alueen kattoa. Se validoi jokaisen A1-viittauksen ja aktiivisen indeksin ennen kuin se ottaa worksheetin kirjoituslukon, ja se palauttaa arvon False aiemmalla valinnalla ennallaan jos jokin on väärin muodostunut

Miksi HotXLS kirjoittaa suuren worksheetin valinnan useana BIFF8 Selection-tietueena: 8 224 tavun runkorajaan mahtuu 9 kiinteää tavua plus 1369 kuusitavuista RefU-aluetta, joten 3 000 aluetta muodostaa kolme saman panelin tietuetta 1369, 1369 ja 262, ja irefAct indeksoi yhdistettyä järjestystä, joten 1370 aluetta joista viimeinen on aktiivinen antaa molemmille tietueille irefAct 1369
Jokainen erä toistaa saman aktiivisen solun ja indeksin, HotXLS:n lukija liittää peräkkäiset saman panelin tietueet yhdeksi ryhmäksi, ja aluetarkistus ajetaan vasta EOF-tietueessa kun koko järjestys on tiedossa
var
  Book: TXLSWorkbook;
  Sheet: TXLSWorksheet;
  Diffs: TXLSSelectedAreas;
  I: Integer;
begin
  Book := TXLSWorkbook.Create;
  try
    Sheet := Book.Sheets.Add;
    Sheet.FreezePanes(1, 1);           // otsikkorivi ja sarake A pysyvät paikallaan

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

    // Jäädytys nollaa tallennetun valinnan, joten valitse jäädytyksen jälkeen.
    // 3000 aluetta tallentuu kolmena Selection-tietueena: 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;

Mitä pane-tavua Selection-tietue käyttää?

Selection-tietue tunnistaa panelinsa formaatin määrittelemällä numeerisella koodilla: 0 on oikea alakulma, 1 oikea yläkulma, 2 vasen alakulma ja 3 vasen yläkulma. Julkinen enumerointi TXLSPanePosition on määritelty lukujärjestyksessä, xlspTopLeft, xlspTopRight, xlspBottomLeft, xlspBottomRight, joten Ord(xlspTopLeft) on 0, joka on tiedostossa oikean alakulman pane. Enumin suora castaus pane-tavuun kirjoittaisi jokaisen vasemman yläkulman valinnan oikean alakulman panelille ilman mitään virheilmoitusta. Jokainen HotXLS:n pane-tietoinen sisääntulopiste konvertoi enumin eksplisiittisen case-lauseen kautta, joten kutsujat eivät koskaan joudu tekemisiin numeeristen koodien kanssa. Panen olemassaolo tarkistetaan myös: oikea yläkulma on olemassa vain pystysplitissä, vasen alakulma vain vaakasplitissä ja oikea alakulma vain molemmissa. Panelille jota nykyinen split- tai jäädytysgeometria ei sisällä, SelectAreas palauttaa arvon False, ja GetSelectedAreas palauttaa tyhjän taulukon jossa ActiveAreaIndex on -1, luomatta pane-rakennetta, valintaobjektia tai solua työkirjaan

Miten HotXLS kuvaa TXLSPanePositionin BIFF8 Selection-tietueen pane-tavuksi: enumi on määritelty lukujärjestyksessä joten Ord(xlspTopLeft) on 0, kun taas tiedosto määrittelee 0:n oikeaksi alakulmaksi, 1:n oikeaksi yläkulmaksi, 2:n vasemmaksi alakulmaksi ja 3:n vasemmaksi yläkulmaksi, joten jokainen pane-tietoinen sisääntulopiste konvertoi eksplisiittisen case-lauseen kautta
Enumin suora castaus pane-tavuun kirjoittaisi jokaisen vasemman yläkulman valinnan oikean alakulman panelille, joten HotXLS tarkistaa myös panen olemassaolon nykyistä split- tai jäädytysgeometriaa vasten ennen kirjoitusta

Missä jokaisen panelin vierityspositio asuu?

Jokaisen panelin vierityspositio jakautuu kahdelle tietueelle, koska neljällä panelilla on käytettävissään vain kaksi rivipositiota ja kaksi sarakepositiota. Klassisessa työkirjassa ylempien panelien ensimmäinen näkyvä rivi ja vasempien panelien ensimmäinen näkyvä sarake ovat Window2.rwTop ja Window2.colLeft, kun taas alempien panelien rivi ja oikeiden panelien sarake ovat Pane.rwTop ja Pane.colLeft. ScrollWindow(xlspTopRight, R, C) kirjoittaa siksi sekä Window2.rwTopin että Pane.colLeftin, ja oikean yläkulman panelin sarakkeen asettaminen siirtää myös oikean alakulman panelia, aivan niin kuin kaksi jakaa yhden vaakavierityspalkin Excelissä. Julkiset metodit käyttävät yhdestä alkavia rivi- ja sarakenumeroita. Puuttuva pane palauttaa arvon False ja asettaa molemmat kyselyn tulokset nollaan, ja alueen ulkopuolinen koordinaatti hylätään ennen kuin kumpikaan akseli muuttuu. Mikään tässä ei riipu siitä miten katselin piirtää ruudukon. Renderöintikontrolli pitää omat TopRow- ja LeftCol-arvonsa, kuten artikkeli työkirjojen renderöinnistä omassa VCL-ruudukossa kertoo, ja ne ovat ajonaikaista tilaa, ei sitä mitä tallennetaan

Missä jokaisen HotXLS:n pane-vieritysakseli asuu: neljä panelia jakaa kaksi rivi- ja kaksi sarakepositiota, joten ylempi rivi ja vasen sarake ovat Window2.rwTop ja Window2.colLeft kun taas alempi rivi ja oikea sarake ovat Pane.rwTop ja Pane.colLeft, ja ScrollWindow(xlspTopRight, 1, 6) kirjoittaa yhden Window2-kentän plus yhden Pane-kentän joten oikea alakulma seuraa perässä
XLSX levittää saman datan sheetView- ja pane-elementtien topLeftCell-attribuuteille, ja kahden kerroksen laskeminen yhdeksi on juuri se tapa jolla ylempi tai vasen vierityspositio katoaa hiljaisesti latauksessa

XLSX levittää saman datan kahdelle elementille: sheetView/@topLeftCell (ECMA-376 Part 1, §18.3.1.87) koko ikkunalle ja lapsielementti pane/@topLeftCell (§18.3.1.66) splitin oikealle alakulmaosalle. Molemmat attribuutit voivat olla läsnä yhtä aikaa. HotXLS lukee uloimman attribuutin ensin ikkuntatason kenttiin, antaa pane-lapsen korvata vain pane-tason kentät ja kirjoittaa molemmat takaisin erikseen. Kahden kerroksen yhdistäminen yhdeksi on juuri se tapa jolla ylempi tai vasen vierityspositio katoaa hiljaisesti latauksessa. Worksheet-kopiot kantavat molemmat kerrokset kummassakin koneessa. Vanhemmat sisääntulopisteet säilyttävät alkuperäisen käyttäytymisensä: klassiset ScrollRow- ja ScrollColumn-ominaisuudet sekä nollapohjaiset XLSX:n SetPaneScroll ja GetPaneScroll. Jäädytys- ja splitgeometria itsessään konfiguroidaan sheet-tason asetuksilla joista kertoo sheetin suojaus, sivun asettelu ja tulostus

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

  // Oikea alakulma: alempi riviakseli (Pane.rwTop) ja oikea sarakeakseli (Pane.colLeft)
  Sheet.ScrollWindow(xlspBottomRight, 500, 3);

  // Oikea yläkulma jakaa oikean sarakeakselin, joten tämä siirtää myös oikean alakulman sarakkeelle 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]));
    // Oikea alakulma alkaa riviltä 500, sarakkeesta 6
end;

Mitä tapahtuu kun Selection-tietue on korruptoitunut?

Kun Selection-tietue on korruptoitunut, HotXLS pitää sen läpinäkymättöminä tavuina, raportoi diagnostiikkakoodin 1304 (xlsDiagnosticSelectionRecordInvalid) ja kirjoittaa alkuperäisen rungon takaisin tavu tavulta tallennuksessa. Ennen kuin tietue liittyy panelinsa ryhmään, lukija tarkistaa sen järjestyksessä. Pane-tavun on oltava 3 tai vähemmän. Yhden panelin tietueiden on oltava yhtenäisiä streamissa. 9 kiinteän tavun on oltava läsnä. crefin on oltava välillä 1–1369, ja rungon on oltava täsmälleen 9 + cref × 6 tavun pituinen. Jokaisen ryhmän erän on oltava samaa mieltä aktiivisesta solusta ja irefActista, irefActin etumerkkibittiä ei saa olla asetettuna, aktiivisen sarakkeen on oltava ruudukossa, eikä millään alueella saa olla käänteisiä rajoja. Yhden fyysisen tietueen ongelmat raportoidaan kerran tietuetta kohti. Ristiriidat jotka ilmenevät vasta aggregaation jälkeen, kuten irefAct joka osoittaa kokonaisalueiden määrän yli tai aktiivinen solu indeksoidun alueen ulkopuolella, raportoidaan kerran ryhmää kohti EOF:ssa. Virheellinen ryhmä pysyy näkymättömänä tyypitetylle API:lle: GetSelectedAreas palauttaa tyhjän taulukon indeksillä -1 kyseiselle panelille, kun taas jokainen muu pane jatkaa toimintaansa

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;

Miten valinnat selviävät rivi- ja sarakeinsertioista?

Valinnat selviävät rakennemuutoksista, koska kokonaisten rivien tai sarakkeiden lisääminen tai poistaminen remappaa jokaisen edustetun pane-ryhmän sekä klassisessa että XLSX-koneessa yhden jaetun remappajan kautta. Selviävät alueet säilyttävät järjestyksensä ja aktiivinen alue säilyttää identiteettinsä. Jos aktiivinen alue poistetaan, ensimmäisestä selviävästä seuraajasta tulee aktiivinen, sitten viimeisestä selviävästä edeltäjästä jos mikään ei seuraa sitä. Jos jokainen alue poistetaan, ryhmä luhistuu yhdeksi soluksi poistorajalle, ja aktiivinen solu joka ei enää osu valitun alueen sisään siirtyy kyseisen alueen vasempaan yläkulmaan, joten indeksi ja koordinaatti eivät koskaan ole ristiriidassa keskenään. Rajat ovat tarkoituksellisia. Virheelliset klassiset ryhmät ohitetaan remappauksessa sen sijaan että ne kirjoitettaisiin uudelleen keksityksi valinnaksi, joten niiden alkuperäiset tavut kulkevat edelleen läpi. Yhden panelin muokkaus korvaa vain kyseisen panelin tietueet ja jättää muut tavutarkasti ennalleen. ODS ei saa lainkaan pane-valintatilaa, koska ODF:llä ei ole vastaavaa worksheet-näkymärakennetta joka kantaisi sitä

Jos sovelluksesi kirjoittaa .xls-tiedostoja jotka käyttäjät avaavat ja joiden sisällä on liikuttava, olipa kyse merkittyjen solujen katselusta, siitä mihin jäätiin jatkamisesta tai jäädytetyn dashboardin jakamisesta, pane-tietoinen valinta- ja vieritys-API kuuluu HotXLS Delphi spreadsheet component -komponenttiin, ja se toimii samalla tavalla XLS:lle ja XLSX:lle