Substream PivotCache BIFF menyimpan dataset cache PivotTable secara terpisah dari view yang menampilkannya, dan HotXLS membaca serta menulis substream itu dengan memeriksa body record alih-alih mempercayai nomor record. Distingsi itulah seluruh kisahnya: nomor record yang sama membawa dua tata body yang tak kompatibel bergantung pada writer mana yang menghasilkan file itu, jadi reader memutuskan framing dari body record pertama yang dilihatnya
Anda berpapasan dengan lapisan ini begitu sebuah PivotTable harus selamat dari round trip. Pivot view tanpa cache-nya adalah cangkang, dan Excel akan membangun ulang cache dari source range ketika membuka file itu — tidak masalah sampai titik di mana source range-nya hilang, datanya ditempel dari sebuah query, atau workbook-nya adalah close terarsip yang tak boleh berubah ketika seseorang membukanya
Dua struktur, dua tempat di dalam file
Data cache dan definisi cache tinggal di bagian berbeda workbook, dan mencampuradukkan keduanya adalah hal pertama yang harus benar. Record cache membentuk substream miliknya sendiri, diberikan di [MS-XLS] §2.1.7.12 sebagai PIVOTCACHE = SXDB SXDBEx *SXFORMULA *FDB *DBB EOF. Perhatikan yang absen: tidak ada BOF di kepala produksi itu
Definisinya duduk di workbook globals sebagai gantinya, sebagai PIVOTCACHEDEFINITION = SXStreamID SXVS [SXSRC] [SXADDLCACHE] (§2.1.7.20.3), diposisikan setelah record formatting dan sebelum record BoundSheet dan Country. Jadi satu cache dideskripsikan di dua tempat yang terpisah ratusan record, dan penghubungnya adalah stream identifier yang harus sepakat di tiga lokasi sekaligus
Tiap cache berada di stream di bawah _SX_DB_CUR yang namanya adalah ejaan heksadesimal huruf besar empat digit dari identifiernya. SXStreamID.idStm, field idstm yang diulang di header SXDB, dan nama stream itu harus semuanya cocok. Ketika Anda mengalokasikan identifier baru, reservasi dulu setiap nomor yang sudah dibaca dari file, atau cache baru bisa mengklaim nomor yang milik cache lebih tua yang belum dijangkau reader
Satu identifier lagi menjebak orang. Nilai iCache di pivot view adalah posisi basis nol dari SXStreamID yang bersesuaian dalam urutan global, bukan identifier cache yang boleh Anda pilih. Saat menulis, ia harus dipetakan dari objek cache ke posisi output aktualnya, dan view yang sudah ada harus dinomori ulang bersamanya, atau meningkatkan satu cache diam-diam mengarahkan sebuah view ke cache yang lain
var
Book: TXLSWorkbook;
Cache: TXLSPivotCache;
Field: TXLSPivotCacheField;
V: TXLSPivotCacheValue;
begin
Book := TXLSWorkbook.Create(nil);
try
Book.LoadFromFile('sales.xls');
Cache := Book.PivotCaches.Add;
Cache.SourceRangeSheet := 'Data';
Cache.SourceFirstRow := 1; Cache.SourceFirstCol := 1;
Cache.SourceLastRow := 500; Cache.SourceLastCol := 6;
Cache.SourceDataType := 1; // SXVS SHEET, MS-XLS 2.4.317
Cache.RefreshOnLoad := False; // percayai record cache-nya
Cache.SaveData := True;
Field := Cache.AddField('Region', xlpcftString);
V.ValueType := xlpcftString;
V.StrValue := 'North';
Field.FindOrAddItem(V);
Cache.SetRecordCount(0); // nolkan, lalu ukur grid record
Cache.SetRecordCount(500);
Book.StorePivotCaches;
finally
Book.Free;
end;
end;
SetRecordCount ganda itu bukan takhayul. RecordCount adalah penulisan property biasa yang tidak mengalokasikan, dan jalur pertumbuhan internalnya hanya menginisialisasi baris yang baru ditambahkan, sehingga cache yang hitungannya diset lewat jalur header bisa berakhir dengan grid indeks berpanjang nol. Penulisan ke RecordIndices kemudian dibuang tanpa error. Menyetel hitungan ke nol dan kembali membangun ulang grid itu, dan itu harus terjadi setelah setiap field ditambahkan, karena lebar barisnya datang dari jumlah field
Mengapa nomor record tak bisa memberi tahu tata body?
Karena nomor record dan tata body berubah pada waktu yang berbeda, sehingga pemetaan di antara keduanya bukan sebuah fungsi. Satu nomor di himpunan legacy hanya pernah muncul di file dari writer lebih tua, yang menjadikannya sinyal yang andal di satu arah. Nomor lain benar-benar ambigu: ia muncul baik di file yang benar maupun di rentang versi perantara yang memakai nomor baru dengan tata body lama
Karena itu framing harus diputuskan dari body, dan sekali per substream cache alih-alih per record. HotXLS mengunci dialek dari panjang record SXDBB pertama di tiap substream. Dalam framing spesifikasi, satu SXDBB menampung persis satu record cache, sehingga panjangnya sama dengan lebar satu baris. Dalam framing packed yang lebih tua, record pertama menampung sebanyak mungkin baris, jadi untuk cache dengan lebih dari satu baris panjangnya sekurangnya dua lebar baris. Perbandingannya menentukan kapan pun kedua prediksi itu berbeda
Ketika keduanya tidak berbeda, reader mengambil pembacaan spesifikasi, dengan prinsip bahwa file yang ditulis Excel melebihi jumlahnya file yang ditulis build perantara. Titik buta itu sempit dari konstruksinya dan, ketika memang terjadi, file itu sendiri tetap diputar ulang byte demi byte. Hanya indeks bertipe yang diekspos ke pemanggil yang terpengaruh
Lebar indeks tinggal di record yang berbeda
SXDBB (§2.4.276) membawa satu indeks per field cache yang flag distinct-value-nya disetel, dalam urutan field, dan lebar tiap indeks diputuskan di tempat lain: record field SXFDB yang bersesuaian (§2.4.283) mendeklarasikan flag short-items, dan flag itu menyatakan apakah indeksnya menempati dua byte atau satu. Dua record, satu kontrak tersirat, dan satu kalimat di spesifikasi yang menghubungkan keduanya
Pengaitan itu persis tempat encoding buatan sendiri keliru. Writer HotXLS sebelumnya mem-packing tiap field ke jumlah bit minimum, menambah padding hingga batas byte antar baris — bisa dipertahankan secara terisolasi dan langsung bertentangan dengan lebar yang ditetapkan writer yang sama di SXFDB sebelumnya. Field dengan tiga nilai distinct dideskripsikan selebar satu byte di satu record dan menempati dua bit di record lain. Perbaikannya bukan mengoreksi aritmetikanya, melainkan mengekstrak keputusan lebar ke satu fungsi yang dipanggil kedua emitter, sehingga kedua record tak bisa lagi saling menjauh. Itu kelas cacat yang sama yang dideskripsikan di penyimpangan deklarasi panjang record BIFF, tempat ukuran yang dideklarasikan dan body yang nyata saling berpisah
Konsekuensi tidak membaca record-record ini sama sekali layak dieja, karena gampang diremehkan. Ketika reader melewati indeks record, setiap cache yang dimuat dari file melaporkan indeks nol untuk tiap field dari tiap baris, artinya setiap baris menunjuk nilai pertama dari setiap field. Itu bukan sekadar introspeksi yang berkurang: jalur evaluasi pivot dan jalur pengisian cache-ke-sel sama-sama mengonsumsi grid itu. Dan test round-trip tak bisa mendeteksinya, karena cache yang masih pada replay mentah ditulis kembali dari byte aslinya
// Flag provenance memberi tahu apa yang Anda pegang dan apa yang boleh ditulis ulang
if Cache.FromRawBlobs then
begin
Writeln('stream id : ', IntToHex(Cache.StreamId, 4));
Writeln('legacy framing : ', Cache.RawFramingIsLegacy);
Writeln('own storage : ', Cache.RawHasStorageStream);
Writeln('model complete : ', Cache.RawModelIsComplete);
// Emit ulang hanya lossless ketika tiap record punya model di sini
if Cache.CanUpgradeFraming then
Writeln('safe to rewrite with the current emitters');
end;
Kapan menulis ulang sebuah cache lossless?
Hanya ketika tiga kondisi terpenuhi bersama, dan CanUpgradeFraming adalah satu-satunya property yang menjawab pertanyaan itu. Cache harus masih pada replay mentah, substreamnya harus berada di salah satu framing yang dulu ditulis pustaka ini secara keliru, dan reader harus sudah membangun model bertipe yang lengkap atas tiap record di dalamnya. Cache yang ditulis Excel tidak pernah memenuhi syarat, karena substreamnya membawa record yang tak punya model di HotXLS, dan emit ulang dari model akan menjatuhkannya
Test kelengkapannya lebih ketat daripada kesan pertamanya. Record yang reader simpan hanya sebagai byte opak menandai model tidak lengkap. Begitu pula hitungan record formula yang dideklarasikan tapi tak bisa direproduksi emitter, karena emit ulang akan menulis ulang deklarasi beberapa record formula menjadi deklarasi nol, dan nilai dalam file yang tak bisa direproduksi setara dengan record yang tak bisa direproduksi
Konservatisme yang disengaja juga mengalir di writer. Indeks di-clamp ke rentang yang sah alih-alih dienkode sebagai sentinel di luar pita, karena spesifikasi mendefinisikan indeks ke urutan distinct-value dan tidak ada yang lain, dan sel kosong itu sendiri adalah sebuah nilai dalam urutan itu. Body record cache yang melampaui plafon record BIFF tidak ditulis sama sekali — itu butuh ribuan field cache dan tak terjangkau dalam batas kolom BIFF8 sekalipun; fallback-nya adalah Excel me-refresh dari source range, yang merupakan perilaku terdefinisi alih-alih file rusak
Tanggal membawa dependensi lintas record terakhir. Konversi serial-ke-tanggal bergantung pada sistem tanggal workbook, dan emitter record tak bisa melihat workbook, sehingga pilihan tanggal dasar diberikan sebagai parameter yang bawaannya sistem 1900 dan disuplai oleh jalur save level workbook. Di bawah sistem 1900 nomor serialnya adalah nilai itu langsung; sistem 1904 berbeda 1462 hari. Perlakuan yang lebih luas atas serial tanggal ada di serial tanggal, sistem 1904, dan number format
Kalau Anda bekerja di lapisan view alih-alih lapisan cache, record-record yang mendeskripsikan pivot yang terlihat dibahas di himpunan record PivotTable BIFF8, dan perilaku sisi perhitungan di calculated field, calculated item, dan refresh. Ketiga lapisan tersedia dalam komponen spreadsheet Delphi HotXLS, yang membuatnya mungkin memuat workbook legacy, memeriksa apa yang sebenarnya dikandung cache-nya, dan memutuskan apakah menulisnya ulang aman sebelum Anda melakukannya