HotXLS membaca sumber .xls, .xlsx, .xlsm, .ods, CSV, dan TSV lewat satu pull row cursor, TXLSRowCursor, yang FindFirst dan FindNext-nya maju satu baris logis sekaligus sementara hanya baris itu yang tinggal di memori. State machine enam nilai memisahkan before-first dari EOF, cancelled, dan faulted, dan pembaca callback yang lebih tua kini menjadi adapter di atas cursor yang sama
Skenarionya akrab bagi siapa pun yang pernah mengirim fitur impor. Sebuah .xlsx 200 MB tiba, Anda memasang handler OnCell, dan persyaratan pertama setelah "bacalah" adalah "berhenti setelah seratus postingan terbalik pertama". Kini bentuk kode Anda melawan Anda: loop itu tinggal di dalam pustaka, handler Anda harus mengangkat sebuah flag, setiap callback berikutnya tetap menyala sampai parser menyadari, dan state yang terakumulasi — berapa kecocokan sejauh ini, kolom mana yang cocok, lalu apa selanjutnya — harus tinggal di field sebuah kelas yang ada hanya untuk memberi callback tempat duduk. Tak ada satu pun dari itu yang soal parsing. Itu soal kontrol aliran, dan itulah yang dihapus pull cursor
Apa yang sebenarnya dibayar callback push pada 200 MB
Push membalikkan kontrol, dan pembalikan itulah yang tak mampu dibeli pemanggil yang memfilter atau menggabungkan. Dengan API callback pustaka memiliki loop, jadi pemanggil tak bisa memakai Break, tak bisa menyisipkan dua sumber, tak bisa menyerahkan pembaca ke rutinitas yang mengharapkan digerakkan, dan tak bisa mengekspresikan "mengintip baris berikutnya sebelum memutuskan" tanpa buffering. Biayanya bukan throughput — jalur callback SAX yang ditulis baik mengalirkan dengan baik — melainkan bahwa setiap konsumen non-trivial menumbuhkan state machine kecil miliknya sendiri untuk mensimulasikan loop yang tak diizinkan ditulis. Kalikan itu dengan empat format file, masing-masing dulu punya titik masuk pemindaian sendiri, dan semantik filter, formula, serta error mulai menyimpang di antara mereka, persis hanyut yang hendak ditutup HotXLS
Bagaimana pull cursor mengubah kode pemanggil Anda?
Ia mengembalikan loop kepada Anda, beserta kontrol aliran Pascal biasa. TXLSRowCursor.Open menerima nama file atau TStream, mendeteksi format, memuat shared string dan metadata gaya-tanggal sekali, dan memilih sheet 1. SelectSheet (1-based) atau SelectSheetByName mengarahkan ulang ke worksheet lain dan me-reset cursor ke before-first. FindFirst dan FindNext kemudian memposisikan di baris terisi berikutnya — baris tanpa sel ter-decode dilewati, sehingga RowIndex bisa melompat — dan baris saat ini diekspos sebagai CellCount, Cells[], dan ValueByCol[], semuanya 1-based pada sumbu kolom. Meninggalkan loop adalah Break
var
Cursor: TXLSRowCursor;
Hits: Integer;
begin
Cursor := TXLSRowCursor.Create;
try
Cursor.FirstRow := 2; // skip the header band
Cursor.IncludeColumn(1); // decode only these two columns
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; // ordinary Break; no abort flag, no sentinel
end;
until not Cursor.FindNext;
finally
Cursor.Free; // the destructor ends the pass
end;
end;
Proyeksi dan rentang diatur sebelum pass, bukan difilter setelahnya. FirstRow, LastRow, IncludeColumn, ClearColumnProjection, IncludeFormulaText, DetectDates, dan DetectTextTypes semuanya dihormati di dalam backend, sehingga kolom yang tak dipilih tak pernah mengalokasikan nilai, string formula, atau payload rich-text-nya sejak awal — regresi suite membuktikannya dengan formula 16 KiB dan string cache yang tak pernah dimaterialkan ketika kolomnya tak diproyeksikan. Opsi-opsi itu sengaja dibekukan selama pass aktif dan jadi bisa ditulis lagi di EOF, pada SelectSheet, atau setelah Close, sehingga satu pemindaian tak pernah bisa mencampur dua kontrak dekode. Jika Anda hanya butuh inventaris sheet alih-alih barisnya, pemuatan metadata-only dan sheet selektif adalah titik masuk yang lebih murah
Satu backend per format, satu loop pemindaian masing-masing
Setiap format punya tepat satu pemindai maju di dalam HotXLS, dan baik pull cursor maupun pembaca callback menggerakkan pemindai yang sama. TXLSXForwardRowBackend adalah satu-satunya state machine SAX worksheet untuk part sheet ECMA-376 Part 1 §18.3, memegang pembaca XML, tabel shared-formula, dan parser rich-text, serta maju tepat satu batas fisik <row> per panggilan. TXLSBiffForwardParser memiliki global, pemilihan sheet, dan maju baris untuk stream record [MS-XLS]; menjadikannya dapat dipause melahirkan kendala paling tajam di seluruh desain, karena string formula cache adalah record Formula yang langsung diikuti record String, sehingga titik penangguhan per-baris tak boleh pernah mendarat di antara keduanya. TXLSForwardTextBackend menyimpan pembaca sadar-BOM, delimiter aktif, dan satu record logis — CSV mengendus koma, titik koma, tab, atau pipa dari record pertama sambil mengabaikan karakter kutip, dan field kutip multi-baris disambung dengan #10 sehingga nomor baris mengikuti record logis alih-alih newline fisik. TXLSForwardOdsBackend menyimpan satu templat baris fisik untuk tabel OpenDocument §9, memperlakukan table:number-rows-repeated sebagai sisa-hitungan alih-alih penguraian, dan maju melewati sel tercakup tanpa menulis nilai. Pembaca langsung streaming berbagi loader shared-string dan gaya-tanggal yang sama
Mengapa enam keadaan alih-alih satu flag Eof?
Karena satu boolean membuat empat situasi berbeda tak terbedakan, dan pemanggil menebak salah di semuanya. TXLSRowCursorState menamai mereka secara eksplisit
xrcsClosed— tak ada sumber yang terbukaxrcsBeforeFirst— terbuka atau baru diarahkan ulang, belum ada baris dibacaxrcsActive— berdiri di baris validxrcsEof— sheet dikonsumsi sampai habisxrcsCancelled— pemanggil menghentikan pass dengan sengajaxrcsFaulted— pass gagal dan exception asli sudah dilempar
Bedanya yang terakhir itulah yang penting di produksi. Part worksheet yang hilang atau awal pass yang gagal menahan EReadError-nya dan memindahkan cursor ke xrcsFaulted; ia tak pernah diturunkan menjadi False polos yang akan dibaca pemanggil sebagai "sheet ini kosong". Cancel sengaja lebih sempit dari Close: ia menutup backend worksheet saat ini beserta substream inflate-nya dan membatalkan baris saat ini, tetapi tak melepaskan arsip ZIP atau stream sumber, dan memanggilnya dua kali adalah no-op. Setelah cancel Anda melanjutkan dengan memanggil SelectSheet secara eksplisit — cursor tak akan diam-diam me-restart pass untuk Anda. Kepemilikan stream mengikuti aturan defensif yang sama: xsoBorrowed adalah bawaan dan memulihkan posisi stream saat tutup, xsoOwned mengalihkan kepemilikan hanya setelah Open sudah sukses, jadi open yang gagal tak pernah membebaskan stream yang masih dipegang pemanggil
var
Cursor: TXLSRowCursor;
Src: TFileStream;
begin
Src := TFileStream.Create('quarter.ods', fmOpenRead or fmShareDenyWrite);
try
Cursor := TXLSRowCursor.Create;
try
// xsoBorrowed: the cursor never frees Src, and Close restores the
// position the stream had when Open was called
if not Cursor.Open(Src, xffAuto, xsoBorrowed) then
Exit;
if Cursor.FindFirst then
repeat
if UserPressedStop then
begin
Cursor.Cancel; // closes the worksheet backend and its
Break; // inflate substream only; idempotent
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; // still ours, still valid, position restored
end;
end;
Meminjam baris saat ini tanpa menyalinnya
IXLSRowCursorView menyerahkan baris ke rutinitas lain tanpa menduplikasi array sel. View menyimpan guard bersama yang memegang pointer cursor plus counter generasi UInt64; maju, memilih sheet, cancel, close, dan menghancurkan cursor semuanya menaikkan generasi itu, dan penghancuran tambahan mengosongkan pemilik guard. Jadi view basi tak bisa membaca memori yang sudah bebas: Valid adalah probe tanpa-exception yang bisa dipanggil kapan pun, sementara setiap anggota lain memvalidasi lebih dulu dan melempar EXLSRowCursorViewInvalidated. Bersikap jujur tentang apa kontrak ini — itu fail-fast masa hidup, bukan jaminan thread-safety, dan ia tak memberi izin membaca sebuah baris dari thread kedua sementara thread pertama memajukan cursor
var
View: IXLSRowCursorView;
Cell: TXLSRowCursorCell;
I: Integer;
begin
if Cursor.FindFirst then
repeat
View := Cursor.CurrentRowView; // borrows; no cell array is copied
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 reads keep the
else if Cell.Kind = xdkEmpty then // cached result, not the tokens
UseStyleOnly(Cell.StyleIndex) // Blank / MulBlank are real cells
else
UseValue(Cell.Col, Cell.Value);
end;
until not Cursor.FindNext;
// The interface outlives the loop, but the row behind it does not
if not View.Valid then // Valid never raises; Cells[] now would raise
View := nil; // EXLSRowCursorViewInvalidated
end;
PeakRowBufferedBytes, dan apa yang diizinkan dibuktikannya
PeakRowBufferedBytes ada untuk menunjukkan bahwa memori mengikuti lebar baris alih-alih jumlah baris. Ia mengakumulasi record sel, Variant, string formula, dan payload rich-text dari baris output saat ini, serta melipat set kerja spesifik-format — record logis CSV, templat baris fisik ODS, puncak record BIFF, atau sel mentah XLSX yang sedang di-decode. Bacalah bersama SheetPassesStarted, yang menghitung berapa pass worksheet yang benar-benar dimulai. Dua peringatan menjaga ini jujur: angkanya estimasi, bukan pembukuan heap eksak, dan ia monotonik sejak Open terakhir, jadi ia instrumen debug dan regresi alih-alih pengukur langsung. Untuk gambaran lebih luas ke mana waktu dan byte pergi pada buku yang sangat besar, lihat kinerja workbook besar di Delphi
Pembaca push menjadi adapter, dan yang tak akan dilakukan cursor
TXLSForwardReader tak lagi membawa titik masuk pemindaian XLSX, BIFF, dan teks terpisah. Ia mengonfigurasi cursor, menyusurinya, dan menerjemahkan baris saat ini menjadi event OnSheet dan OnCell, dan itulah mengapa kedua fasad tak bisa lagi saling menyimpang pada filter, state formula, atau penanganan error. Dua konsekuensi layak diketahui sebelum Anda upgrade: callback SheetIndex kini seragam 1-based pada TXLSForwardReader (TXLSDirectReader mempertahankan kontrak event 0-based-nya yang ada), dan OnSheet menyala sebelum SelectSheet, sehingga mengatur SkipSheet berarti part worksheet tak pernah dibuka atau didekompresi sama sekali. Batas-batasnya sama eksplisitnya: workbook tidak boleh dimodifikasi selama pass aktif, cancel menuntut restart eksplisit, dan jalur maju BIFF tak pernah men-decompile token formula, sehingga sel formula klasik melaporkan HasFormula true dengan FormulaTextAvailable false dan menyerahkan hasil cache alih-alih mengarang string formula kosong. Row cursor dan adapter-nya lolos 1.298 pemeriksaan di Delphi Win32 dan Win64 plus paket statis C++Builder 37.0 Win64
Jika Anda sedang menimbang pull cursor versus loader yang Anda pegang sekarang, pertanyaannya bukan mana yang parse lebih cepat melainkan mana yang membiarkan Anda menulis kondisi keluar yang benar-benar Anda butuhkan. Detail komponen penuh, versi IDE yang didukung, dan lisensi ada di halaman komponen spreadsheet Delphi HotXLS