HotXLS čte zdroje .xls, .xlsx, .xlsm, .ods, CSV a TSV jedním tahacím kurzorem řádků TXLSRowCursor, jehož FindFirst a FindNext posouvají po jedné logické řádce, přičemž v paměti zůstává jen tento řádek. Šestihodnotový stavový automat rozlišuje stav před prvním řádkem od konce souboru, zrušení a chyby a starší čtečka s callbacky je dnes adaptérem nad stejným kurzorem
Scénář zná každý, kdo někdy dodával funkci importu. Přijde .xlsx o 200 MB, napojíte handler OnCell a prvním požadavkem po „přečti to" je „zastav se po prvních sta obrácených účtování". Tvář vašeho kódu vám teď brání: smyčka žije uvnitř knihovny, handler musí nastavit příznak, všechny další callbacky se stále spouštějí, dokud to parser nepozná, a nashromážděný stav — kolik zásahů doposud, který sloupec seděl, co dál — musí žít v polích třídy, která existuje jen proto, aby měla callback kde sedět. Na ničem z toho není problém parsování. Je to problém řízení toku a právě ten tahací kurzor odstraňuje
Co ve skutečnosti stojí push callback při 200 MB
Push obrací řízení a právě inverzi si volající, který filtruje nebo slučuje, nemůže dovolit. S callback API vlastní smyčku knihovna, takže volající nemůže použít Break, nemůže prokládat dva zdroje, nemůže předat čtečku rutině, která očekává, že ji bude někdo řídit, a nemůže bez bufferování vyjádřit „nejprve se podívej na další řádek a teprve pak se rozhodni". Cena není v propustnosti — dobře napsaná SAX cesta streamuje v pořádku — jde o to, že každý netriviální konzument si vypěstuje vlastní malý stavový automat, aby nasimuloval smyčku, kterou mu napsat nebylo dovoleno. Vynásobte to čtyřmi formáty souborů, z nichž každý má historicky vlastní vstupní bod skenování, a sémantika filtrování, vzorců a chyb začne mezi nimi rozcházet — právě ten rozchod chtělo HotXLS zavřít
Jak tahací kurzor mění váš volající kód?
Vrací vám smyčku, a s ní obyčejný řídicí tok Pascalu. TXLSRowCursor.Open přijme název souboru nebo TStream, rozpozná formát, jednou načte sdílené řetězce a metadata stylů data a vybere list 1. SelectSheet (indexace od 1) nebo SelectSheetByName přesměruje na jiný list a nastaví kurzor zpět před první řádek. FindFirst a FindNext pak stanou na další obsazené řádce — řádky bez dekódovatelných buněk se přeskočí, takže RowIndex může skákat — a aktuální řádek je vystaven jako CellCount, Cells[] a ValueByCol[], vše s indexací sloupců od 1. Opuštění smyčky je Break
var
Cursor: TXLSRowCursor;
Hits: Integer;
begin
Cursor := TXLSRowCursor.Create;
try
Cursor.FirstRow := 2; // přeskoč záhlaví
Cursor.IncludeColumn(1); // dekóduj jen tyto dva sloupce
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; // obyčejné Break; žádný příznak přerušení, žádný sentinel
end;
until not Cursor.FindNext;
finally
Cursor.Free; // destruktor průchod ukončí
end;
end;
Projekce a rozsah se nastavují před průchodem, nikoli filtrují potom. FirstRow, LastRow, IncludeColumn, ClearColumnProjection, IncludeFormulaText, DetectDates a DetectTextTypes respektují backendy uvnitř, takže nevybraný sloupec nikdy nealokuje svou hodnotu, řetězec vzorce ani rich-text data — regresní sada to dokazuje vzorci 16 KiB a cachovanými řetězci, které se nikdy nevytvoří, když jejich sloupec není projektován. Tyto volby jsou za aktivního průchodu záměrně zamrzlé a znovu se stanou zapisovatelnými na konci souboru, při SelectSheet nebo po Close, takže jeden sken nikdy nemíchá dvě dekódovací smlouvy. Pokud potřebujete jen inventář listů a ne řádky, levnějším vstupním bodem je načítání jen metadat a vybraných listů
Jeden backend na formát, jedna skenovací smyčka
Každý formát má uvnitř HotXLS přesně jeden dopředný skener a tahací kurzor i čtečka s callbacky řídí tentýž skener. TXLSXForwardRowBackend je jediný SAX stavový automat listu pro části listů ECMA-376 Part 1 §18.3, drží čtečku XML, tabulku sdílených vzorců a parser rich-textu a posune se na přesně jednu fyzickou hranici <row> na volání. TXLSBiffForwardParser vlastní globály, výběr listu a posun řádků pro proud záznamů [MS-XLS]; umožnit mu pauzu vyneslo nejostrější omezení celého návrhu, protože cachovaný řetězcový vzorec je záznam Formula bezprostředně následovaný záznamem String, takže bod pozastavení na řádek nesmí nikdy spadnout mezi ně. TXLSForwardTextBackend drží čtečku známou s BOM, aktivní oddělovač a jednu logickou položku — CSV z první položky vyčuchá čárku, středník, tabulátor nebo svislítko a ignoruje znaky v uvozovkách, a víceřádkové pole v uvozovkách se spojí s #10, takže číslo řádku sleduje logické položky, nikoli fyzické konce řádků. TXLSForwardOdsBackend drží jednu fyzickou šablonu řádku pro tabulky OpenDocument §9, bere table:number-rows-repeated jako zbývající počet, nikoli expanzi, a posouvá se přes pokryté buňky bez vydávání hodnot. Streamovací přímá čtečka sdílí stejný zavaděč sdílených řetězců a stylů data
Proč šest stavů místo jednoho příznaku Eof?
Protože jediná boolean hodnota učiní čtyři různé situace nerozlišitelnými a volající se u všech mýlí. TXLSRowCursorState je pojmenuje explicitně
xrcsClosed— žádný zdroj není otevřenxrcsBeforeFirst— otevřeno nebo přesměrováno, žádný řádek ještě načtenxrcsActive— stojí na platném řádkuxrcsEof— list byl spotřebován až do koncexrcsCancelled— volající průchod záměrně zastavilxrcsFaulted— průchod selhal a původní výjimka byla vyvolána
Právě to poslední rozlišení hraje roli v produkci. Chybějící část listu nebo nezdařený start průchodu si podrží své EReadError a přesune kurzor do xrcsFaulted; nikdy se nesníží na holé False, které by volající četl jako „tento list byl prázdný". Cancel je záměrně užší než Close: zavře backend aktuálního listu a jeho podproud inflace a zneplatní aktuální řádek, ale neuvolní ZIP archiv ani zdrojový proud a dvojí volání je no-op. Po zrušení pokračujete explicitním voláním SelectSheet — kurzor za vás tiše průchod nerestartuje. Vlastnictví proudu následuje totéž obranné pravidlo: xsoBorrowed je výchozí a při zavření obnoví pozici proudu, xsoOwned přenese vlastnictví jen poté, co Open již uspělo, takže neúspěšné otevření nikdy neuvolní proud, který volající stále drží
var
Cursor: TXLSRowCursor;
Src: TFileStream;
begin
Src := TFileStream.Create('quarter.ods', fmOpenRead or fmShareDenyWrite);
try
Cursor := TXLSRowCursor.Create;
try
// xsoBorrowed: kurzor Src nikdy neuvolní a Close obnoví
// pozici, kterou proud měl při volání Open
if not Cursor.Open(Src, xffAuto, xsoBorrowed) then
Exit;
if Cursor.FindFirst then
repeat
if UserPressedStop then
begin
Cursor.Cancel; // zavře jen backend listu a jeho
Break; // podproud inflace; idempotentní
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; // stále náš, stále platný, pozice obnovena
end;
end;
Vypůjčení aktuálního řádku bez kopírování
IXLSRowCursorView předá řádek jiné rutině bez duplikování pole buněk. Pohled ukládá sdílenou stráž držící ukazatel kurzoru plus UInt64 počítadlo generace; posun, výběr listu, zrušení, zavření i zničení kurzoru generaci zvyšují a zničení navíc vymaže vlastníka stráže. Zastaralý pohled tedy nemůže číst uvolněnou paměť: Valid je sond bez výjimek, kterou lze volat kdykoli, zatímco každý ostatní člen se nejprve validuje a vyvolá EXLSRowCursorViewInvalidated. Buďte upřímní co je tato smlouva — je to fail-fast životnosti, ne záruka bezpečnosti vláken, a nelicencuje čtení řádku z druhého vlákna, zatímco první posouvá kurzor
var
View: IXLSRowCursorView;
Cell: TXLSRowCursorCell;
I: Integer;
begin
if Cursor.FindFirst then
repeat
View := Cursor.CurrentRowView; // půjčí; pole buněk se nekopíruje
for I := 0 to View.CellCount - 1 do
begin
Cell := View.Cells[I];
if Cell.HasFormula and not Cell.FormulaTextAvailable then
UseCachedResult(Cell.Value) // dopředné čtení BIFF podrží
else if Cell.Kind = xdkEmpty then // cachovaný výsledek, ne tokeny
UseStyleOnly(Cell.StyleIndex) // Blank / MulBlank jsou skutečné buňky
else
UseValue(Cell.Col, Cell.Value);
end;
until not Cursor.FindNext;
// Rozhraní přežije smyčku, ale řádek za ní ne
if not View.Valid then // Valid nikdy nevyvolá; Cells[] teď by vyvolala
View := nil; // EXLSRowCursorViewInvalidated
end;
PeakRowBufferedBytes a co smí prokázat
PeakRowBufferedBytes existuje k tomu, aby demonstroval, že paměť sleduje šířku řádku, nikoli počet řádků. Akumuluje záznamy buněk, Variants, řetězce vzorců a rich-text data aktuálního výstupního řádku a zahrne formátově specifickou pracovní sadu — CSV logickou položku, šablonu fyzického řádku ODS, špičku záznamů BIFF nebo surovou buňku XLSX právě dekódovanou. Čtěte jej spolu s SheetPassesStarted, které počítá, kolik průchodů listem skutečně začalo. Dvě varování drží údaj poctivým: číslo je odhad, ne přesné účtování haldy, a je monotónní od posledního Open, takže jde o ladicí a regresní nástroj, nikoli živý měřič. Širší obraz o tom, kam jdou čas a bajty u velmi velkých sešitů, přináší článek výkonnost velkých sešitů v Delphi
Push čtečka se stala adaptérem a co kurzor neudělá
TXLSForwardReader už nenese samostatné vstupní body skenování pro XLSX, BIFF a text. Nakonfiguruje kurzor, projde jej a přeloží aktuální řádek do událostí OnSheet a OnCell, takže obě fasády se už nemohou rozcházet v filtrování, stavu vzorců ani zpracování chyb. Dva důsledky stojí za znalost před upgradem: callback SheetIndex je nyní na TXLSForwardReader jednotně indexován od 1 (TXLSDirectReader si podrží svou stávající smlouvu událostí od 0) a OnSheet se spustí před SelectSheet, takže nastavení SkipSheet znamená, že část listu se vůbec neotevře ani nerozbalí. Hranice jsou stejně explicitní: sešit nesmí být za aktivního průchodu upraven, zrušení vyžaduje explicitní restart a dopředná cesta BIFF nikdy nedekompiluje tokeny vzorců, takže klasické buňky se vzorcem hlásí HasFormula true s FormulaTextAvailable false a podávají cachovaný výsledek, místo aby vymyslely prázdný řetězec vzorce. Kurzor řádků a jeho adaptér prošly 1 298 kontrolami na Delphi Win32 a Win64 plus statickém balíčku C++Builder 37.0 Win64
Pokud vážíte tahací kurzor proti zavaděči, který máte nyní, otázka není, který parsuje rychleji, ale který vám dovolí napsat výstupní podmínku, kterou skutečně potřebujete. Úplné detaily komponenty, podporované verze IDE a licencování jsou na stránce HotXLS Delphi spreadsheet component