HotXLS Delphi Component hanya me-replay chart Excel yang tidak dimodifikasi byte per byte ketika dua hal terpenuhi: chart-nya dijangkau lewat relationship drawing worksheet dan bukan lewat nama part yang ditebak, dan fingerprint model 64-bit ditangkap setelah parsing model chart selesai. Versi 2.382.0 memperbaiki syarat pertama, versi 2.382.3 memperbaiki syarat kedua dan mulai me-round-trip offset anchor xdr:colOff dan xdr:rowOff bernilai bukan nol yang selama ini selalu di-hard-code ke nol oleh writer drawing. Kedua cacat itu muncul dari satu kasus korpus lokal, two-charts.xlsx: mula-mula sebuah assertion struktural melihat dua part chart menjadi tiga, lalu perbandingan byte setiap xl/charts/chartN.xml menunjukkan chart yang tidak disentuh siapa pun tetap ditulis ulang — dan tidak satu pun dari keduanya melempar exception atau membuat Excel mengeluh, itulah sebabnya keduanya bertahan selama ini
Kenapa workbook dua chart kembali dengan tiga part chart?
Karena loader punya fallback yang menebak. Ketika sebuah worksheet tidak punya relationship drawing di part .rels-nya, kode lama mengasumsikan drawing berada di nama konvensional xl/drawings/drawing{i+1}.xml, dengan i adalah posisi sheet, lalu melampirkan part itu jika memang ada di arsip. Di two-charts.xlsx sheet pertama tidak punya drawing dan tidak punya part .rels sama sekali, sementara xl/drawings/drawing1.xml ada — dan itu milik sheet kedua, yang mencapainya lewat Target="../drawings/drawing1.xml". Sheet 1 karena itu mewarisi chart yang tidak pernah ia referensikan, chart1.xml di-parse dua kali, dan save menulis workbook dengan tiga part chart, bukan dua
Perbaikan di HotXLS v2.382.0 menghapus tebakan itu sepenuhnya. Drawing worksheet sekarang dimuat hanya lewat ParPartTargets[i].Values[XlsxRtDrawing], target yang tercatat untuk relationship type drawing di sheet tersebut, dan sheet tanpa relationship semacam itu tidak mendapat drawing sama sekali. Itulah perilaku yang dituntut format: elemen <drawing r:id="…"/> di worksheet (ECMA-376 Part 1 §18.3.1.36) adalah satu-satunya tautan antara sheet dan drawing-nya, dan nama part di package OPC tidak membawa makna apa pun di luar yang diberikan graph relationship. Arsip yang ditulis Excel kebetulan memakai nama konvensional, dan itulah yang membuat shortcut itu lolos begitu lama; pembahasan resolusi relationship OPC di HotXLS membahas kenapa menebak nama part tidak pernah aman walaupun tebakannya biasanya benar
// Sebelum v2.382.0: relationship drawing yang hilang jatuh ke tebakan
drawingName := ParPartTargets[i].Values[XlsxRtDrawing];
if drawingName = '' then
drawingName := 'xl/drawings/drawing' + IntToStr(i + 1) + '.xml';
if zip.Exists(drawingName) then
LoadDrawing(zip, drawingName); // bisa jadi milik sheet lain
// Sejak v2.382.0: relationship atau tidak sama sekali
drawingName := ParPartTargets[i].Values[XlsxRtDrawing];
if (drawingName <> '') and zip.Exists(drawingName) then
LoadDrawing(zip, drawingName);
Apa yang dijamin oleh fingerprint chart?
Fingerprint memutuskan, per chart, apakah save boleh menyalin part aslinya atau harus membangkitkannya ulang. Saat import, dengan PreserveUnsupportedParts aktif sebelum Open, HotXLS menyimpan byte UTF-8 mentah setiap part chart di FRawChartXml, membangun serialisasi milik model bertipe dengan BuildChartKnownXml, lalu menyimpan panjang serialisasi itu di FRawChartModelLength dan hash-nya di FRawChartModelHash. Hash-nya adalah FNV-1a atas code unit UTF-16 dari XML yang dihasilkan, dengan offset basis 64-bit standar 14695981039346656037 dan prime 1099511628211. Saat save, XlsxChartRawModelUnchanged membangun ulang known XML lalu membandingkan panjang dan hash-nya; kecocokan berarti model bertipe itu persis seperti saat import, jadi tidak ada yang bisa diubah aplikasi yang ikut berubah
function XlsxChartRawModelUnchanged(Chart: TXLSXChart;
const KnownXml: WideString): Boolean;
begin
Result := (Chart <> nil) and (Chart.FRawChartXml <> '') and
(Length(KnownXml) = Chart.FRawChartModelLength) and
(XlsxChartModelHash(KnownXml) = Chart.FRawChartModelHash);
end;
function BuildChartXmlFromKnown(Chart: TXLSXChart;
const KnownXml: WideString): WideString;
begin
if Chart.FRawChartXml = '' then
Result := KnownXml // tidak ada yang dipreservasi
else if XlsxChartRawModelUnchanged(Chart, KnownXml) then
Result := XlsxDecodeChartUtf8(Chart.FRawChartXml) // replay verbatim
else
Result := XlsxMergeChartXml(
XlsxDecodeChartUtf8(Chart.FRawChartXml), KnownXml); // merge struktural
end;
Writer XLSX melangkah lebih jauh dari BuildChartXmlFromKnown. Ketika model tidak berubah dan StrictOOXML mati, ia mula-mula mencoba menyalin entry terkompresi langsung dari arsip sumber ke output di bawah nama part chart yang baru, sehingga byte-nya bahkan tidak didekode dan di-deflate ulang. Hanya jika penyalinan itu tidak mungkin, ia jatuh ke jalur decode-atau-merge. Mekanismenya sendiri — panjang plus hash, replay saat sama, merge saat tidak — adalah yang dijelaskan di catatan tentang mengedit chart Excel tanpa kehilangan ChartML. Artikel ini membahas cara mekanisme itu diam-diam berhenti bekerja
Kenapa setiap chart tetap menempuh jalur merge?
Karena fingerprint-nya ditangkap satu panggilan terlalu awal. Parsing chart di HotXLS adalah pass SAX atas part chart yang diikuti sekumpulan pass pemulihan yang menarik detail dari teks mentah yang tidak dimodelkan langsung oleh handler SAX: XlsxChartParseSeriesFlags membaca setiap blok <c:ser> untuk flag <c:smooth>-nya dan nilai srgbClr dari marker fill serta marker line, lalu memulihkan mode perpotongan axis dan gaya tick-mark mayor maupun minor untuk axis kategori dan nilai. Sebelum v2.382.3 urutan di akhir ParseChartXml adalah: klasifikasikan grup axis, bangun known XML, tangkap panjang dan hash, baru jalankan XlsxChartParseSeriesFlags. Fingerprint karena itu mendeskripsikan model yang masih belum punya flag smooth, warna marker, dan tick mark. Saat save, BuildChartKnownXml berjalan atas model yang sudah lengkap, yang kini mengeluarkan <c:smooth val="1"/> dan warna marker yang dipulihkan. XML-nya lebih panjang, hash-nya berbeda, XlsxChartRawModelUnchanged mengembalikan False, dan chart-nya lewat XlsxMergeChartXml. Merge adalah operasi yang benar untuk chart yang diedit seseorang, tapi bukan operasi yang mempertahankan byte: ia menyerialisasi ulang tree, dan aturan kepemilikan yang membiarkan model bertipe menang untuk series, axis, dan plot group berarti node hasil regenerasi menggantikan node aslinya. Hasil yang terlihat di jalannya korpus adalah warna series yang bergeser pada chart yang tidak diedit siapa pun — setiap chart di setiap workbook yang dipreservasi, di setiap save, tanpa diagnostik di mana pun
Perbaikannya cuma satu pengurutan ulang: XlsxChartParseSeriesFlags kini berjalan sebelum known XML dibangun, sehingga fingerprint mendeskripsikan model sebagaimana adanya saat aplikasi pertama kali melihatnya. Pelajarannya berlaku lebih luas dari chart. Fingerprint pendeteksi perubahan hanya sebaik momen pengambilannya, dan momen yang aman adalah setelah setiap pass yang bisa memutasi model selesai. HotXLS punya situs penangkapan kedua untuk dua nilai yang sama, baseline yang ia tetapkan ulang terhadap file output setelah save berhasil, dan situs itu selalu berjalan atas model yang sudah di-parse lengkap; situs saat import itulah yang menyimpang
Ke mana perginya offset anchor?
Ke literal nol. Sebuah twoCellAnchor di part drawing memaku chart di antara dua sel, dan setiap sudutnya membawa indeks sel plus offset di dalam sel itu: from (ECMA-376 Part 1 §20.5.2.5) dan to (§20.5.2.32) masing-masing memegang col, colOff (§20.5.2.4), row, dan rowOff. Offset-nya dalam English Metric Unit, 914400 per inci, dan Excel menulis nilai bukan nol setiap kali chart ditempatkan atau diubah ukurannya dengan mouse, yaitu hampir semua chart. Chart pertama di two-charts.xlsx mulai di row 0 dengan rowOff 19049 dan berakhir di column 8, row 15 dengan colOff 247650 dan rowOff 66674 — sekitar seperempat inci masuk ke kolom terakhir. Parser drawing di HotXLS selalu membaca keempat nilai itu — kode image memakainya — tapi writer chart mengeluarkan <xdr:colOff>0</xdr:colOff> dan <xdr:rowOff>0</xdr:rowOff> untuk setiap sudut, yang menempelkan setiap chart ke grid sel saat save
// Sejak v2.382.3 writer anchor me-replay offset EMU yang diimport
Result := '<xdr:twoCellAnchor' + EditAsAttr + '><xdr:from><xdr:col>' +
IntToStr(Chart.FromCol - 1) + '</xdr:col><xdr:colOff>' +
IntToStr(Chart.FFromColOff) + '</xdr:colOff>' +
'<xdr:row>' + IntToStr(Chart.FromRow - 1) + '</xdr:row>' +
'<xdr:rowOff>' + IntToStr(Chart.FFromRowOff) + '</xdr:rowOff></xdr:from>' +
'<xdr:to><xdr:col>' + IntToStr(Chart.ToCol - 1) + '</xdr:col><xdr:colOff>' +
IntToStr(Chart.FToColOff) + '</xdr:colOff>' +
'<xdr:row>' + IntToStr(Chart.ToRow - 1) + '</xdr:row>' +
'<xdr:rowOff>' + IntToStr(Chart.FToRowOff) + '</xdr:rowOff></xdr:to>' + ...
TXLSXChart kini membawa FFromColOff, FFromRowOff, FToColOff, dan FToRowOff, diisi dari parser drawing dan disalin bersama state anchor lain ketika sebuah chart diassign. Field-field itu sengaja privat: permukaan anchor publik tetap keempat koordinat sel FromRow, FromCol, ToRow, dan ToCol, dan chart yang dibuat dari kode Delphi mendarat di batas sel seperti sebelumnya. Offset-nya ada agar round trip setia, bukan untuk memaparkan penempatan sub-sel sebagai fitur. Perhatikan bahwa perbaikan ini independen dari fingerprint: anchor hidup di part drawing, bukan part chart, jadi chart yang ChartML-nya ter-replay sempurna tetap akan meloncat ke grid tanpanya. Konversi unit di balik nilai EMU itu dibahas di catatan tentang geometri image HotXLS dan penskalaan EMU
Bagaimana membuktikan chart round-trip tanpa berubah?
Dengan membandingkan byte, bukan dengan membuka hasilnya di Excel. Excel memperbaiki dan menormalisasi begitu banyak hal saat memuat sehingga chart yang bergeser tampak baik-baik saja sampai suatu hari seorang analis menyadari warna marker-nya berubah. Uji korpus yang menangkap kedua cacat itu melakukan tiga hal setelah open-and-save tanpa edit: ia menelusuri relationship worksheet, drawing, dan chart lalu gagal pada referensi chart yang duplikat, yatim, atau menggantung; ia membandingkan signature tipe chart, formula series, dan geometri anchor antara aslinya dan outputnya; dan untuk two-charts.xlsx ia membaca setiap xl/charts/chartN.xml dari kedua arsip dan menuntut byte yang identik. Pemeriksaan yang sama mudah ditulis di Delphi dengan TZipFile dari RTL
uses System.Zip, System.SysUtils;
function ChartPartsIdentical(const Original, Resaved: string): Boolean;
var
Src, Dst: TZipFile;
Name: string;
A, B: TBytes;
begin
Result := True;
Src := TZipFile.Create;
Dst := TZipFile.Create;
try
Src.Open(Original, zmRead);
Dst.Open(Resaved, zmRead);
for Name in Src.FileNames do
if Name.StartsWith('xl/charts/chart') and Name.EndsWith('.xml') then
begin
Src.Read(Name, A);
Dst.Read(Name, B); // melempar exception jika part-nya hilang
if (Length(A) <> Length(B)) or
((Length(A) > 0) and not CompareMem(@A[0], @B[0], Length(A))) then
begin
Writeln('changed: ', Name);
Result := False;
end;
end;
finally
Dst.Free;
Src.Free;
end;
end;
Tiga syarat membuat perbandingan itu bermakna, dan masing-masing gagal diam-diam jika terlupakan. PreserveUnsupportedParts harus True sebelum Open, atau tidak ada byte mentah yang ditangkap dan setiap chart dibangun ulang dari model. StrictOOXML harus False, karena strict mode memaksa regenerasi secara desain. Dan aplikasi tidak boleh menyentuh chart antara open dan save — membaca property tidak masalah, tapi setter apa pun yang mengubah model bertipe membalik fingerprint dan mengirim chart ke jalur merge, yang merupakan perilaku benar dan bukan yang diuji di sini. Part chart juga dinomori ulang dari counter se-workbook saat save, jadi workbook yang urutan sheet atau urutan chart-nya berubah akan menempatkan byte identik di bawah nama chartN.xml yang berbeda; pemeriksa korpus mengikuti relationship, bukan nama, karena alasan itu
Kedua perbaikan dirilis di HotXLS 2.382.0 dan 2.382.3 dan sudah diverifikasi di Win32 serta Win64 terhadap korpus lokal, dengan sampel chart hasil save juga di-render lewat office suite independen ke PDF dan dibandingkan halaman per halaman dengan aslinya. HotXLS membaca, mengedit, dan menulis chart XLSX dari kode Delphi dan C++Builder native tanpa perlu instalasi Excel, dan itulah yang menjadikan tingkat kesetiaan seperti ini tanggung jawab library — halaman komponen spreadsheet Delphi HotXLS memuat daftar fitur dan unduhan uji coba