HotXLS čita .xls, .xlsx, .xlsm, .ods, CSV i TSV izvore kroz jedan jedini pull row kursor, TXLSRowCursor, čiji FindFirst i FindNext pomeraju jedan logički red po putu dok samo taj red ostaje u memoriji. Mašina stanja sa šest vrednosti razdvaja stanje pre prvog reda od EOF, otkazanog i neuspešnog prolaza, a stariji čitač sa callback-ovima je sada adapter nad istim kursorom
Scenario je poznat svakome ko je isporučio funkciju uvoza. Stigne 200 MB .xlsx, priključite OnCell rukovaoca, i prvi zahtev posle „pročitaj ga" je „stani posle prvih stotinu reverznih knjiženja". Sada se oblik vašeg koda bori protiv vas: petlja živi unutar biblioteke, vaš rukovalac mora da podigne flag, svaki sledeći callback i dalje se okida dok parser ne primeti, i nakupljeno stanje — koliko pogodaka do sada, koja kolona se poklopila, šta dalje — mora živeti u poljima klase koja postoji samo da bi callback imao gde da sedi. Ništa od toga nije problem parsiranja. To je problem toka upravljanja, i upravo ga pull kursor uklanja
Šta push callback stvarno košta na 200 MB
Push obrće upravljanje, a obrtanje je upravo ono što pozivalac koji filtrira ili spaja ne može sebi da priušti. Sa callback API-jem biblioteka poseduje petlju, pa pozivalac ne može Break, ne može da ispreplće dva izvora, ne može da da čitač rutini koja očekuje da bude vođena, i ne može da izrazi „pogledaj sledeći red pre odluke" bez baferisanja. Cena nije propusnost — dobro napisan SAX callback put strimi sasvim fino — nego što svaki netrivijalni potrošač uzgaja sopstvenu malu mašinu stanja da simulira petlju koju nije smeo da napiše. Pomnožite to sa četiri formata fajla, od kojih je svaki istorijski imao svoju ulaznu tačku skeniranja, i semantika filtriranja, formula i grešaka počinje da se razilazi među njima, a upravo taj razmak HotXLS je nameravao da zatvori
Kako pull kursor menja vaš kod poziva?
Vraća vam petlju, i sa njom običan Pascal tok upravljanja. TXLSRowCursor.Open prima ime fajla ili TStream, detektuje format, jednom učitava deljene stringove i metapodatke o datumskom stilu, i bira list 1. SelectSheet (1-bazirano) ili SelectSheetByName preusmerava na drugi radni list i resetuje kursor na before-first. FindFirst i FindNext zatim pozicioniraju na sledeći naseljeni red — redovi bez dekodabilnih ćelija se preskaču, pa RowIndex može da skoči — a tekući red je izložen kao CellCount, Cells[] i ValueByCol[], svi 1-bazirani po kolonskoj osi. Izlaz iz petlje je Break
var
Cursor: TXLSRowCursor;
Hits: Integer;
begin
Cursor := TXLSRowCursor.Create;
try
Cursor.FirstRow := 2; // preskoči pojas zaglavlja
Cursor.IncludeColumn(1); // dekodiraj samo ove dve kolone
Cursor.IncludeColumn(7);
if not Cursor.Open('postings-200mb.xlsx') then
Exit;
if not Cursor.SelectSheetByName('Ledger') then
Exit;
Hits := 0;
if Cursor.FindFirst then
repeat
if VarToStr(Cursor.ValueByCol[7]) = 'REVERSED' then
begin
Inc(Hits);
if Hits = 100 then
Break; // običan Break; bez abort flaga, bez sentinela
end;
until not Cursor.FindNext;
finally
Cursor.Free; // destruktor završava prolaz
end;
end;
Projekcija i opseg se postavljaju pre prolaza, ne filtriraju posle. FirstRow, LastRow, IncludeColumn, ClearColumnProjection, IncludeFormulaText, DetectDates i DetectTextTypes se svi poštuju unutar bekendova, pa neprojektovana kolona nikad ne alocira svoju vrednost, formula string ili rich-text sadržaj na prvom mestu — regresiona serija ovo dokazuje sa 16 KiB formulama i keširanim stringovima koji se nikad ne materijalizuju kada njihova kolona nije projektovana. Te opcije su namerno zamrznute dok je prolaz aktivan i ponovo postaju upisljive na EOF, na SelectSheet, ili posle Close, pa jedno skeniranje nikad ne može da pomeša dva ugovora dekodovanja. Ako vam treba samo inventar listova a ne redovi, učitavanje samo metapodataka i selektivno učitavanje listova je jeftinija ulazna tačka
Jedan bekend po formatu, po jedna skenirajuća petlja
Svaki format ima tačno jedan unapredni skener unutar HotXLS-a, i i pull kursor i callback čitač voze taj isti skener. TXLSXForwardRowBackend je jedina worksheet SAX mašina stanja za sheet delove ECMA-376 Part 1 §18.3, drži XML čitač, tabelu deljenih formula i rich-text parser, i napreduje na tačno jednu fizičku <row> granicu po pozivu. TXLSBiffForwardParser poseduje globalse, izbor lista i napredovanje reda za [MS-XLS] tok zapisa; učiniti ga pauzabilnim proizvelo je naj-oštrije ograničenje celog dizajna, jer je keširana string formula Formula zapis odmah praćen String zapisom, pa tačka suspendovanja po redu nikad ne sme da padne između njih dva. TXLSForwardTextBackend drži BOM-svestan čitač, aktivni delimiter i jedan logički zapis — CSV namiruje zarez, tačku-zarez, tab ili pipe iz prvog zapisa ignorišući citirane znakove, i višelinijski citirani poli se spajaju sa #10 tako da broj reda prati logičke zapise a ne fizičke nove linije. TXLSForwardOdsBackend drži jedan fizički šablon reda za OpenDocument §9 tabele, tretira table:number-rows-repeated kao preostali brojač a ne ekspanziju, i napreduje preko pokrivenih ćelija bez emitovanja vrednosti. Streaming direktan čitač deli isti učitalac deljenih stringova i datumskih stilova
Zašto šest stanja umesto jednog Eof flaga?
Jer jedan boolean čini četiri različite situacije nerazlučivim, i pozvaoci pogađaju pogrešno kod svih njih. TXLSRowCursorState ih imenuje eksplicitno
xrcsClosed— nijedan izvor nije otvorenxrcsBeforeFirst— otvoren ili preusmeren, još nijedan red nije pročitanxrcsActive— stoji na važećem reduxrcsEof— list je potrošen do krajaxrcsCancelled— pozivalac je namerno zaustavio prolazxrcsFaulted— prolaz je pao i originalni izuzetak je dignut
To poslednje razlikovanje je ono što biti u produkciji. Nedostajući worksheet deo ili neuspeo početak prolaza zadržava svoj EReadError i pomera kursor na xrcsFaulted; nikad nije unovažen u običan False koji bi pozivalac pročitao kao „ovaj list je bio prazan". Cancel je namerno uži od Close: zatvara tekući worksheet bekend i njegov inflate podtok i poništava tekući red, ali ne otpušta ZIP arhivu ni izvorni tok, i pozivanje ga dva puta je no-op. Posle otkazivanja nastavljate eksplicitnim pozivom SelectSheet — kursor neće tiho restartovati prolaz u vaše ime. Vlasništvo nad tokom prati isto defanzivno pravilo: xsoBorrowed je podrazumevano i vraća poziciju toka pri zatvaranju, xsoOwned prenosi vlasništvo tek kada je Open već uspeo, pa neuspeo open nikad ne oslobađa tok koji pozivalac i dalje drži
var
Cursor: TXLSRowCursor;
Src: TFileStream;
begin
Src := TFileStream.Create('quarter.ods', fmOpenRead or fmShareDenyWrite);
try
Cursor := TXLSRowCursor.Create;
try
// xsoBorrowed: kursor nikad ne oslobađa Src, i Close vraća
// poziciju koju je tok imao kada je Open pozvan
if not Cursor.Open(Src, xffAuto, xsoBorrowed) then
Exit;
if Cursor.FindFirst then
repeat
if UserPressedStop then
begin
Cursor.Cancel; // zatvara worksheet bekend i njegov
Break; // inflate podtok samo; idempotentno
end;
until not Cursor.FindNext;
case Cursor.State of
xrcsEof: Log('sheet consumed to the end');
xrcsCancelled: Log('stopped by the operator');
xrcsFaulted: Log('pass failed; the EReadError was already raised');
end;
finally
Cursor.Free;
end;
finally
Src.Free; // i dalje naš, i dalje važeći, pozicija vraćena
end;
end;
Pozajmljivanje tekućeg reda bez kopiranja
IXLSRowCursorView predaje red drugoj rutini bez dupliciranja niza ćelija. Prikaz čuva deljeni guard koji drži pokazivač kursora plus UInt64 brojač generacije; napredovanje, izbor lista, otkazivanje, zatvaranje i uništavanje kursora svi povećavaju tu generaciju, i uništavanje dodatno briše vlasnika guarda. Tako zastareli prikaz ne može da čita oslobođenu memoriju: Valid je sonda bez izuzetaka koju možete zvati u svako vreme, dok svaki drugi član prvo validira i diže EXLSRowCursorViewInvalidated. Budite iskreni oko toga šta je ovaj ugovor — to je lifetime fail-fast, ne garancija bezbednosti niti, i ne daje dozvolu da se red čita iz druge niti dok prva pomera kursor
var
View: IXLSRowCursorView;
Cell: TXLSRowCursorCell;
I: Integer;
begin
if Cursor.FindFirst then
repeat
View := Cursor.CurrentRowView; // pozajmljuje; niz ćelija se ne kopira
for I := 0 to View.CellCount - 1 do
begin
Cell := View.Cells[I];
if Cell.HasFormula and not Cell.FormulaTextAvailable then
UseCachedResult(Cell.Value) // BIFF forward čitanja zadržavaju
else if Cell.Kind = xdkEmpty then // keširani rezultat, ne tokene
UseStyleOnly(Cell.StyleIndex) // Blank / MulBlank su prave ćelije
else
UseValue(Cell.Col, Cell.Value);
end;
until not Cursor.FindNext;
// Interfejs nadživljava petlju, ali red iza njega ne
if not View.Valid then // Valid nikad ne diže izuzetak; Cells[] sada bi digla
View := nil; // EXLSRowCursorViewInvalidated
end;
PeakRowBufferedBytes, i šta sme da dokazuje
PeakRowBufferedBytes postoji da pokaže da memorija prati širinu reda a ne broj redova. Nakuplja zapise ćelija, Variante, formula stringove i rich-text sadržaje tekućeg izlaznog reda i upliće format-specifičan radni skup — CSV logički zapis, ODS fizički šablon reda, BIFF vrh zapisa, ili XLSX sirovu ćeliju koja se trenutno dekoduje. Čitajte ga zajedno sa SheetPassesStarted, koji broji koliko je worksheet prolaza stvarno počelo. Dve napomene drže ovo pošteno: brojka je procena, ne tačno heap računovodstvo, i monoton je od poslednjeg Open, pa je to instrument za otklanjanje grešaka i regresiju pre nego živi merilac. Za širu sliku gde odlaze vreme i bajtovi na vrlo velikim sveskama, vidite performanse velikih radnih svesaka u Delphi-ju
Push čitač je postao adapter, i šta kursor neće da radi
TXLSForwardReader više ne nosi odvojene XLSX, BIFF i tekst ulazne tačke skeniranja. Konfiguriše kursor, prolazi ga, i prevodi tekući red u OnSheet i OnCell događaje, i zato dve fasade više ne mogu da se razilaze po filtriranju, stanju formula ili rukovanju greškama. Dve posledice vredi znati pre nadogradnje: callback SheetIndex je sada ujednačeno 1-baziran na TXLSForwardReader (TXLSDirectReader zadržava svoj postojeći 0-bazirani ugovor događaja), i OnSheet se okida pre SelectSheet, pa podešavanje SkipSheet znači da worksheet deo nikad nije ni otvoren ni dekomprimovan. Granice su jednako eksplicitne: radna sveska ne sme da se menja dok je prolaz aktivan, otkazivanje traži eksplicitan restart, i BIFF forward putanja nikad ne dekompajlira tokene formula, pa klasične formula ćelije izveštavaju HasFormula true sa FormulaTextAvailable false i predaju vam keširani rezultat umesto da izmišljaju prazan formula string. Row kursor i njegov adapter prošli su 1.298 provera na Delphi Win32 i Win64 plus C++Builder 37.0 Win64 statičkom paketu
Ako vagate pull kursor naspram učitača koji sada imate, pitanje koje treba postaviti nije koji parsira brže nego koji vam dozvoljava da napišete uslov izlaska koji stvarno trebate. Kompletni detalji komponente, podržane IDE verzije i licenciranje su na stranici HotXLS Delphi spreadsheet komponente