HotXLS Delphi Component menyimpan baris ODS yang membawa table:number-rows-repeated dan tinggi baris sebagai satu record TXLSXRowHeightRun — baris pertama, baris terakhir, satu tinggi — alih-alih satu entri tinggi per baris berulang, dan melipat style sel kosong yang diwarisi baris-baris itu menjadi satu interval style overlay. Itulah alasan HotXLS 2.382.2 membuka spreadsheet yang ekornya berulang 1.048.530 baris kosong dalam 0,02 detik sementara 2.382.1 timeout, dan kenapa file yang sama tersimpan kembali ke ODS dengan repeat count utuh alih-alih sejuta baris literal
File yang dimaksud biasa saja. LibreOffice Calc menulis sheet empat belas kolom dengan 45 baris data, lalu mendeskripsikan semua yang di bawahnya dengan satu elemen: <table:table-row table:style-name="ro1" table:number-rows-repeated="1048530"><table:table-cell table:number-columns-repeated="14"/></table:table-row>. Style ro1 menetapkan style:row-height="0.452cm", dan setiap <table:table-column> membawa table:default-cell-style-name yang diwarisi setiap sel kosong di run itu. Seluruh content.xml-nya 103 KB. Tidak ada apa pun di file itu yang berteriak "mahal"; mahalnya sepenuhnya dari sisi kami
Kenapa satu baris berulang membuat import ODS timeout?
Karena importer-nya dulu mengembangkannya. Di 2.382.1 row finisher mengulang SetRowHeight(RowIndex + i, RowHeight) sekali per baris berulang, menuliskan setiap tinggi ke string list Name=Value yang diindeks berdasarkan nomor baris. Setiap penyisipan ke list itu menjalankan pencarian IndexOfName atas semua yang sudah ada di dalamnya, jadi sejuta tinggi berbiaya sejuta pemindaian linear — pencarian list kuadratik yang menjadi dasar pengajuan HXLS-005. Di saat yang sama OdsCommitRow mewujudkan objek sel untuk setiap kolom yang mewarisi style, di setiap baris berulang itu, karena sel kosong yang berstyle tetap dihitung sebagai sel
Sisi save punya versi masalahnya sendiri. File LibreOffice itu diakhiri satu baris ro1 lagi setelah pengulangan besarnya, jadi baris berstyle tertinggi berada di dasar sheet, dan OdsBuildTableXml menelusuri setiap baris sampai ke sana sambil mengeluarkan elemen <table:table-row> satu per satu. Bahkan workbook yang diimport dengan murah akan ditulis dengan mahal. Memperbaiki import tanpa memperbaiki export hanya akan memindahkan timeout-nya, bukan menghapusnya
Apa itu row-height run di HotXLS?
Run adalah hal terkecil yang bisa mendeskripsikan "baris 46 sampai 1.048.575 semuanya setinggi 12,81 poin" tanpa mengatakannya 1.048.530 kali. TXLSXRowHeightRun adalah record berisi FirstRow, LastRow, dan Height; TXLSXRowHeightRuns adalah array dinamis berisi record itu, dan setiap TXLSXWorksheet menyimpan satu di FRowHeightRuns di samping daftar tinggi per baris yang sudah ada. Pada import ODS, row finisher kini bercabang pada repeat count-nya: hitungan 1 tetap memanggil SetRowHeight, apa pun yang lebih besar memanggil XlsxAssignRowHeightRun sekali untuk seluruh rentangnya. Rentangnya dijepit ke XlsxMaxRow, yaitu 1.048.576, sehingga repeat count yang melewati batas sheet dipotong, bukan ditolak
XlsxAssignRowHeightRun adalah satu-satunya penulis array itu, dan ia menjaga run-nya tetap lepas satu sama lain secara konstruksi. Diberi interval baru, ia menyalin setiap run yang sepenuhnya berada di luarnya, memecah run yang bertumpang tindih menjadi bagian sebelum dan bagian sesudah, lalu menambahkan interval barunya ketika Present bernilai true — atau tidak menambahkan apa pun ketika Present false, dan begitulah ClearRowHeight melubangi satu baris. Dua hal mengikutinya. Array-nya tidak pernah memuat interval yang bertumpang tindih, jadi pencarian bisa berhenti pada kecocokan pertama. Dan array-nya tidak pernah diubah di tempat; salinan baru dibangun di setiap pemanggilan, yang tidak berbiaya pada ukuran seperti ini dan menghapus satu kelas bug aliasing
var
Workbook: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
begin
Workbook := TXLSXWorkbook.Create;
try
// Sheet yang baris ekornya berulang 1.048.530 kali di bawah satu style baris
Workbook.OpenODS('conditional-formatting.ods');
Sheet := Workbook.Sheets[1];
// Kedua pembacaan diselesaikan lewat run yang sama; tidak ada yang dikembangkan
Writeln(Sheet.RowHeight[46]:0:2, ' pt');
Writeln(Sheet.RowHeight[1048575]:0:2, ' pt');
// Override satu baris menutupi run tanpa memecahnya
Sheet.RowHeight[500000] := 36;
// Menghapus satu baris di dalam run memotong run menjadi dua bagian
Sheet.ClearRowHeight(500001);
Writeln(Sheet.HasRowHeight(500001)); // False
Writeln(Sheet.RowHeight[500002]:0:2, ' pt'); // masih tinggi run
finally
Workbook.Free;
end;
end;
Urutan pencariannya adalah bagian yang layak dihafal. TXLSXWorksheet.GetRowHeight memeriksa daftar per baris lebih dulu dan baru mengonsultasikan run ketika baris itu tidak punya entri eksplisit, dan HasRowHeight melakukan hal yang sama. Jadi Sheet.RowHeight[500000] := 36 tidak menyentuh run-nya sama sekali — ia menambahkan satu entri ke daftar per baris, dan entri itu menang karena diperiksa lebih dulu. ClearRowHeight sebaliknya: ia menghapus entri per baris mana pun lalu memanggil XlsxAssignRowHeightRun dengan Present = False, karena baris yang dihapus tingginya harus terbaca sebagai "tanpa tinggi" walaupun ada run yang mencakupnya. ClearRowHeights mengosongkan kedua struktur itu sekaligus
Ke mana perginya style sel kosong yang diwarisi?
Ke satu interval style overlay per kolom, bukan ke objek sel. OdsCommitRow memutuskan per nilai kolom apakah ia sel kosong yang ringkas: barisnya berulang lebih dari sekali, selnya tidak punya nilai, formula, maupun rich text. Untuk sel kosong yang ringkas ia membuat sel sungguhan hanya di baris pertama run-nya, menerapkan style warisannya ke situ, lalu mendaftarkan enam indeks style yang sama — font, fill, border, number format, alignment, protection — sebagai StyleOverlays.Add yang mencakup baris dua sampai akhir run di kolom itu. Baris-baris setelah yang pertama dilewati sepenuhnya di loop materialisasinya
Uji regresinya membuat bentuknya konkret. Setelah membuka sheet yang baris keduanya berulang 1.048.575 kali di bawah style default kolom yang bold, Sheet.Cells.Count ditegaskan berada di bawah 10, dan Sheet.Cells[700000, 1].FontIndex tetap teresolusi ke font bold-nya — overlay-nya menyediakan style itu begitu koordinat tersebut disentuh. Ini mekanisme yang sama yang menjaga kolom berformat tapi kosong agar tidak berbiaya sejuta sel di sisi XLSX; catatan tentang penyimpanan sel row-block dan interval style overlay membahas bagaimana overlay bertumpuk dan teresolusi. Yang baru di sini adalah importer ODS membuatnya sendiri, dari repeat count-nya, alih-alih menunggu aplikasi memformat sebuah range
Bagaimana SaveAsODS menulis repeat count-nya kembali?
Dengan memecah ekor kosong sheet-nya hanya di tempat sesuatu benar-benar berubah. OdsBuildTableXml kini melacak dua batas: contentMaxRow, baris terakhir yang memuat nilai, formula, hyperlink, atau manual row break, dan maxRow, yang tambahan mencakup sel kosong yang hanya berstyle, tinggi satu baris, LastRow setiap run, dan tepi bawah setiap overlay. Sel kosong yang hanya berstyle tidak lagi dihitung sebagai konten — TXLSXCells.IsStyleOnlyBlank yang mengecualikannya — sehingga baris berstyle di ekor file LibreOffice itu berhenti menyeret batas kontennya ke dasar sheet
Di atas contentMaxRow baris ditulis satu per satu persis seperti sebelumnya. Di bawahnya writer menghitung nextRow sebagai nilai terkecil dari: FirstRow run berikutnya, LastRow + 1 run saat ini, entri tinggi satu baris berikutnya, tepi overlay berikutnya, dan sel termaterialisasi berikutnya. Semua dari baris saat ini sampai nextRow - 1 lalu dikeluarkan sebagai satu <table:table-row> dengan table:number-rows-repeated diset ke selisihnya, membawa satu <table:table-cell/> per kolom dengan nama style hasil resolusi overlay ketika ada overlay yang mencakup kolom itu. Style barisnya sendiri datang dari TOdsAutoStylePool.RowStyleFor(AHidden, ABreakBefore, AHeightSpec), yang kini melipat teks tinggi barisnya — 12.81pt, misalnya — ke dalam kunci deduplikasinya bersama flag hidden dan page-break, sehingga setiap baris di run itu berbagi satu style ro<N> dengan satu properti style:row-height
var
Workbook, Reopened: TXLSXWorkbook;
Saved: TMemoryStream;
begin
Workbook := TXLSXWorkbook.Create;
Reopened := TXLSXWorkbook.Create;
Saved := TMemoryStream.Create;
try
Workbook.OpenODS('conditional-formatting.ods');
Workbook.Sheets[1].RowHeight[500000] := 36;
Workbook.Sheets[1].ClearRowHeight(500001);
// Ekor kosongnya ditulis sebagai segenggam baris berulang, bukan sejuta
Workbook.SaveAsODS(Saved);
Writeln('ODS size: ', Saved.Size, ' bytes');
Saved.Position := 0;
Reopened.Open(Saved);
// Override, lubang dan run semuanya bertahan melewati round trip
Writeln(Reopened.Sheets[1].RowHeight[500000]:0:2); // 36.00
Writeln(Reopened.Sheets[1].HasRowHeight(500001)); // False
Writeln(Reopened.Sheets[1].RowHeight[500002]:0:2); // tinggi run
finally
Saved.Free;
Reopened.Free;
Workbook.Free;
end;
end;
Uji yang mengunci ini menegaskan stream hasil save-nya di bawah 64 KB untuk sheet yang height run-nya membentang 1.048.575 baris dengan satu override dan satu lubang yang dilubangi di tengahnya. Dua batas yang jujur perlu disebut di sebelah angka itu. Pertama, worksheet dengan data validation apa pun menetapkan contentMaxRow ke maxRow, jadi validation menonaktifkan pemadatan ekor di sheet tersebut dan sheet-nya ditulis baris per baris lagi. Kedua, XLSX tidak punya atribut repeat — satu <row> SpreadsheetML mendeskripsikan satu baris — jadi mengekspor sheet yang ditopang run ke .xlsx mengenumerasi baris yang dicakup run-nya dan menulis atribut ht di masing-masingnya. Modelnya tetap ringkas di memori; format filenya yang menentukan bagaimana file itu terlihat
Apa yang sekarang harus dibayar setiap editan penomoran ulang baris ke run-nya?
Pemeliharaan. Representasi baru untuk metadata baris hanya benar jika setiap operasi yang mengubah nomor baris ikut memindahkannya bersama daftar per baris di sebelahnya, dan commit-nya menyentuh masing-masing operasi itu. InsertRows dan DeleteRows melewati XlsxShiftRowHeightRuns, yang membangun ulang array-nya dengan mempertahankan bagian setiap run yang berada sebelum titik edit, membuang apa pun yang jatuh di dalam jendela delete, dan menambahkan kembali sisanya yang digeser sebesar delta-nya — sehingga run yang mengapit sebuah penyisipan menjadi dua run dengan celah, dan run yang mengapit penghapusan menyusut. TileRangeAxisMetadata mengosongkan run-nya di seluruh rentang yang di-tile, lalu mendaftarkan ulang setiap run sumber sekali per salinan pada offset-nya. TXLSXWorksheet.CopyFrom dan TXLSXSheets.AddCopy mengambil Copy() dari array-nya alih-alih mengassignnya, itulah sebabnya uji itu bisa mengosongkan semua tinggi pada klonnya dan tetap menemukan sheet aslinya utuh di baris 1.048.576
var
Sheet: TXLSXWorksheet;
begin
Sheet := Workbook.Sheets[1];
Sheet.RowHeight[500000] := 36;
Sheet.ClearRowHeight(500001);
// Sisipkan dua baris di 500000: override-nya pindah ke 500002, lubangnya ke 500003
Sheet.InsertRows(500000, 2);
Writeln(Sheet.RowHeight[500002]:0:2); // 36.00
Writeln(Sheet.HasRowHeight(500003)); // False
// Hapus lagi: semuanya bergeser balik
Sheet.DeleteRows(500000, 2);
Writeln(Sheet.RowHeight[500000]:0:2); // 36.00
// Tile baris 2..4 dua kali ke bawah sheet; tinggi run mengikuti setiap salinan
Sheet.TileRangeAxisMetadata(2, 1, 3, 1, 2, 1);
Writeln(Sheet.RowHeight[7]:0:2); // tinggi run
end;
Batas di sisi baca punya kewajiban yang sama. GetUsedRange menaikkan tepi bawahnya ke FirstRow dan LastRow setiap run, dan BuildRowMajorCellOrder memperluas baris maksimumnya yang inklusif metadata melewati setiap run agar writer XLSX tetap mengunjungi baris yang hanya punya tinggi. Kalau suatu saat Anda menambahkan struktur berbasis kunci baris sendiri di atas model objek HotXLS, inilah daftar periksanya: insert, delete, tile, copy, used range, dan setiap serializer. Lewatkan satu saja dan kegagalannya senyap — tingginya bergeser sebesar jumlah penyisipannya, dan tidak ada yang melempar exception
Apa yang tetap per baris, dan seperti apa angkanya sekarang
Flag hidden, outline level, dan state collapsed masih dikembangkan. Row finisher mengulang SetRowHidden dan SetRowOutlineLevel sekali per baris berulang, jadi sheet yang menyembunyikan ekor sejuta baris, atau menyarangkannya di dalam table:table-row-group, membayar satu entri per baris untuk masing-masing atribut itu. Perubahan 2.382.2 dibatasi pada dua hal yang benar-benar diukur HXLS-005 — tinggi baris dan style kosong yang diwarisi — dan teknik run yang sama akan berlaku untuk yang lain kalau suatu saat ada file yang menuntutnya. Reader ODS-nya juga tidak bertindak atas style:use-optimal-row-height; style baris yang menyatakan "optimal" sekaligus memberi tinggi akan diimport dengan tinggi itu
Terhadap korpusnya, conditional-formatting.ods kini menyelesaikan siklus open, assert, save, reopen, dan re-assert dalam 0,178 detik di Win32 dan 0,158 detik di Win64, dengan tahap open-nya sendiri 0,020 detik, di dalam anggaran 60 detik yang sebelumnya ia habiskan. Antarmuka level workbook yang dilalui format ini diuraikan di pembahasan membuka dan menyimpan file ODS, dan kumpulan tuas yang lebih luas untuk file besar di performa workbook besar; elemen baris ODF itu sendiri, beserta atribut repeat dan style-nya, dispesifikasikan di ODF 1.3 Part 3 §9.1.4
HotXLS membaca dan menulis XLS, XLSX serta ODS dari kode Delphi dan C++Builder native tanpa Excel atau LibreOffice terpasang, itulah sebabnya pengulangan sejuta baris adalah sesuatu yang harus dimodelkan dengan baik oleh library alih-alih diserahkan ke proses eksternal — halaman komponen spreadsheet Delphi HotXLS mencantumkan format yang didukung dan versi RAD Studio