HotXLS čita .xls, .xlsx, .xlsm, .ods, CSV i TSV izvore kroz jedinstveni pull row cursor, TXLSRowCursor, čiji FindFirst i FindNext napreduju po jedan logički redak dok u memoriji ostaje samo taj redak. Automat sa šest stanja razdvaja prije-prvog retka od EOF, otkazanog i neuspjelog, a stariji čitač s povratnim pozivom sada je adapter nad istim kurzorom
Scenarij je poznat svakome tko je isporučio značajku uvoza. Stigne 200 MB .xlsx, uključite rukovatelj OnCell, a prvi zahtjev nakon „pročitaj to" jest „zaustavi se nakon prvih stotinu obrnutih knjiženja". Sad vam oblik koda pruža otpor: petlja živi unutar biblioteke, vaš rukovatelj mora podići zastavicu, svaki kasniji callback i dalje se aktivira dok parser ne primijeti, a nakupljeno stanje — koliko pogodaka do sada, koji je stupac pogodio, što dalje — mora živjeti u poljima klase koja postoji samo da bi callbacku dala gdje sjesti. Ništa od toga nije problem parsiranja. To je problem toka upravljanja, i to je upravo ono što pull cursor uklanja
Što push callback stvarno košta pri 200 MB
Push okreće upravljanje, a inverzija je upravo ono što pozivatelj koji filtrira ili spaja ne može priuštiti. S callback API-jem biblioteka posjeduje petlju, pa pozivatelj ne može koristiti Break, ne može isprepliti dva izvora, ne može predati čitač rutini koja očekuje da bude vođena i ne može izraziti „pogledaj sljedeći redak prije odluke" bez međuspremanja. Cijena nije propusnost — dobro napisana SAX callback putanja struji uredno — nego što svaki netrivijalni potrošač uzgaja vlastiti mali automat da bi simulirao petlju koju mu nije bilo dopušteno napisati. Pomnožite to s četiri formata datoteka, od kojih je svaki povijesno imao vlastitu ulaznu točku skeniranja, i semantika filtriranja, formula i grešaka počinje među njima ići nasumce, a upravo to rasulo HotXLS je krenuo zatvoriti
Kako pull cursor mijenja vaš pozivni kod?
Vraća vam petlju, a s njom i obični Pascal tok upravljanja. TXLSRowCursor.Open prima ime datoteke ili TStream, otkriva format, jednom učitava dijeljene nizove i metapodatke o stilu datuma te bira list 1. SelectSheet (1-based) ili SelectSheetByName preusmjerava na drugi radni list i vraća kurzor na prije-prvog. FindFirst i FindNext zatim se pozicioniraju na sljedeći popunjeni redak — redovi bez dekodirajućih ćelija preskaču se, pa RowIndex može skakati — a trenutni redak izložen je kao CellCount, Cells[] i ValueByCol[], svi 1-based na osi stupaca. Izlaz iz petlje jest Break
var
Cursor: TXLSRowCursor;
Hits: Integer;
begin
Cursor := TXLSRowCursor.Create;
try
Cursor.FirstRow := 2; // preskoči zaglavni pojas
Cursor.IncludeColumn(1); // dekodiraj samo ova dva stupca
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čni Break; bez zastavice prekida, bez sentinela
end;
until not Cursor.FindNext;
finally
Cursor.Free; // destruktor završava prolaz
end;
end;
Projekcija i raspon postavljaju se prije prolaza, a ne filtriraju poslije. FirstRow, LastRow, IncludeColumn, ClearColumnProjection, IncludeFormulaText, DetectDates i DetectTextTypes svi se poštuju unutar pozadinskih sustava, pa neodabrani stupac nikad ne alocira svoju vrijednost, niz formule ili rich-text sadržaj na prvom mjestu — regresijski paket to dokazuje formulama od 16 KiB i predmemoriranim nizovima koji se nikad ne materijaliziraju kad njihov stupac nije projiciran. Te opcije namjerno su zamrznute dok je prolaz aktivan i ponovno postaju upisive pri EOF, na SelectSheet ili nakon Close, pa jedno skeniranje nikad ne može pomiješati dva ugovora o dekodiranju. Ako trebate samo popis listova, a ne retke, učitavanje samo metapodataka i selektivno učitavanje listova jeftinija je ulazna točka
Jedan backend po formatu, po jedna skenirajuća petlja
Svaki format ima točno jedan skener naprijed unutar HotXLS-a, a i pull cursor i callback čitač pogone taj isti skener. TXLSXForwardRowBackend jest jedini SAX automat radnog lista za dijelove listova ECMA-376 Part 1 §18.3, drži XML čitač, tablicu dijeljenih formula i parser rich-texta, i napreduje do točno jedne fizičke granice <row> po pozivu. TXLSBiffForwardParser posjeduje globalse, izbor lista i napredovanje retka za tok zapisa [MS-XLS]; učiniti ga pauzirajućim proizvelo je najstrože ograničenje u cijelom dizajnu, jer je predmemorirana string formula zapis Formula kojeg odmah slijedi zapis String, pa točka suspendiranja po retku nikad ne smije pasti između njih dvaju. TXLSForwardTextBackend drži čitač svjestan BOM-a, aktivni graničnik i jedan logički zapis — CSV njuši zarez, točku-zarez, tabulator ili cjev iz prvog zapisa ignorirajući znakove u navodnicima, a višeredna navodnična polja spajaju se s #10 pa broj retka prati logičke zapise, a ne fizičke nove retke. TXLSForwardOdsBackend drži jedan fizički predložak retka za tablice OpenDocument §9, tretira table:number-rows-repeated kao preostali brojač, a ne kao širenje, i napreduje preko pokrivenih ćelija bez ispuštanja vrijednosti. Streaming direct reader dijeli isti učitavač dijeljenih nizova i stilova datuma
Zašto šest stanja umjesto jedne Eof zastavice?
Jer jedan boolean čini četiri različite situacije nerazlučivima, a pozivatelji pogađaju krivo kod svih njih. TXLSRowCursorState ih imenuje izrijekom
xrcsClosed— nijedan izvor nije otvorenxrcsBeforeFirst— otvoreno ili preusmjereno, još nijedan redak nije pročitanxrcsActive— stoji se na valjanom retkuxrcsEof— list je potrošen do krajaxrcsCancelled— pozivatelj je namjerno zaustavio prolazxrcsFaulted— prolaz je pao i izvorna iznimka je podignuta
To posljednje razlikovanje jest ono što se računa u produkciji. Nedostajući dio radnog lista ili neuspjeli početak prolaza zadržava svoj EReadError i premješta kurzor na xrcsFaulted; nikad se ne degradira u običan False koji bi pozivatelj pročitao kao „ovaj je list bio prazan". Cancel je namjerno uži od Close: zatvara backend trenutnog radnog lista i njegov inflate podtok te poništava valjanost trenutnog retka, ali ne otpušta ZIP arhivu ni izvorni tok, a dvostruki poziv je no-op. Nakon otkazivanja nastavljate izričitim pozivom SelectSheet — kurzor vam neće tiho ponovno pokrenuti prolaz. Vlasništvo nad tokom slijedi isto obrambeno pravilo: xsoBorrowed je zadano i vraća poziciju toka pri zatvaranju, xsoOwned prenosi vlasništvo tek nakon što Open već uspije, pa neuspjeli open nikad ne oslobađa tok koji pozivatelj još drži
var
Cursor: TXLSRowCursor;
Src: TFileStream;
begin
Src := TFileStream.Create('quarter.ods', fmOpenRead or fmShareDenyWrite);
try
Cursor := TXLSRowCursor.Create;
try
// xsoBorrowed: kurzor nikad ne oslobađa Src, a Close vraća
// poziciju koju je tok imao kad je Open pozvan
if not Cursor.Open(Src, xffAuto, xsoBorrowed) then
Exit;
if Cursor.FindFirst then
repeat
if UserPressedStop then
begin
Cursor.Cancel; // zatvara backend radnog lista i njegov
Break; // inflate podtok; 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; // još je naš, još vrijedi, pozicija vraćena
end;
end;
Posuđivanje trenutnog retka bez kopiranja
IXLSRowCursorView predaje redak drugoj rutini bez dupliciranja polja ćelija. Prikaz čuva dijeljeni čuvar koji nosi pokazivač kurzora plus UInt64 brojač generacije; napredovanje, biranje lista, otkazivanje, zatvaranje i uništavanje kurzora svi povećavaju tu generaciju, a uništavanje dodatno briše vlasnika čuvara. Pa zastarjeli prikaz ne može čitati oslobođenu memoriju: Valid je sonda bez iznimki koju možete zvati u svako doba, dok svaki drugi član prvo validira i podiže EXLSRowCursorViewInvalidated. Budite iskreni glede što je ovaj ugovor — to je doživotni fail-fast, a ne garancija sigurnosti dretvi, i ne ovlašćuje čitanje retka iz druge dretve dok prva napreduje kurzorom
var
View: IXLSRowCursorView;
Cell: TXLSRowCursorCell;
I: Integer;
begin
if Cursor.FindFirst then
repeat
View := Cursor.CurrentRowView; // posuđuje; polje ć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 // predmemorirani rezultat, a ne tokene
UseStyleOnly(Cell.StyleIndex) // Blank / MulBlank jesu stvarne ćelije
else
UseValue(Cell.Col, Cell.Value);
end;
until not Cursor.FindNext;
// Sučelje nadživi petlju, ali redak iza njega ne
if not View.Valid then // Valid nikad ne podiže iznimku; Cells[] sada bi
View := nil; // EXLSRowCursorViewInvalidated
end;
PeakRowBufferedBytes i što smije dokazati
PeakRowBufferedBytes postoji da bi pokazao da memorija prati širinu retka, a ne broj redaka. Akumulira zapise ćelija, Variante, nizove formula i rich-text sadržaje trenutnog izlaznog retka i uključuje skup radnog formata — CSV logički zapis, ODS fizički predložak retka, BIFF vršni zapis ili XLSX sirovu ćeliju koja se upravo dekodira. Čitajte ga uz SheetPassesStarted, koji broji koliko je prolaza radnih listova stvarno počelo. Dvije napomene drže ovo iskrenim: brojka je procjena, ne točno računovodstvo hrpe, i monotona je od zadnjeg Open, pa je alat za otklanjanje grešaka i regresiju, a ne živi mjerač. Za širu sliku gdje odlazi vrijeme i bajtovi na vrlo velikim knjigama, pogledajte performanse velikih radnih knjiga u Delphiju
Push čitač postao je adapter, i što kurzor neće učiniti
TXLSForwardReader više ne nosi zasebne ulazne točke skeniranja za XLSX, BIFF i tekst. Konfigurira kurzor, prolazi ga i prevodi trenutni redak u događaje OnSheet i OnCell, pa dvije fasade više ne mogu ići nasumce u filtriranju, stanju formula ili obradi grešaka. Dvije posljedice vrijedi znati prije nadogradnje: callback SheetIndex sada je uniformno 1-based na TXLSForwardReader (TXLSDirectReader zadržava svoj postojeći 0-based dogovor o događajima), a OnSheet se aktivira prije SelectSheet, pa postavljanje SkipSheet znači da se dio radnog lista nikad ne otvara ni ne dekompresira. Granice su jednako izričite: radnu knjigu ne smijete mijenjati dok je prolaz aktivan, otkazivanje zahtijeva izričito ponovno pokretanje, a BIFF forward putanja nikad ne dekompilira tokene formula, pa klasične ćelije formula prijavljuju HasFormula true uz FormulaTextAvailable false i predaju vam predmemorirani rezultat umjesto da izmisle prazan niz formule. Kurzor redaka i njegov adapter prošli su 1.298 provjera na Delphi Win32 i Win64 plus C++Builder 37.0 Win64 statički paket
Ako vagate pull cursor protiv učitavača koji sada imate, pitanje nije koji brže parsira nego koji vam dopušta da napišete uvjet izlaska koji stvarno trebate. Pune pojedinosti komponente, podržane verzije IDE-a i licenciranje nalaze se na stranici HotXLS Delphi komponente za proračunske tablice