Artikel Teknis

Ekspor Lembar Kerja Aman Unicode di Delphi: RTF dan HTML

Sebuah lembar kerja menyimpan satu kolom nama pelanggan. Beberapa ada dalam bahasa Mandarin, beberapa dalam Sirilik, beberapa membawa umlaut Jerman atau aksen Prancis. Anda mengekspornya ke CSV dan membuka hasilnya, dan setiap karakternya utuh. Anda mengekspor buku kerja yang sama ke RTF untuk templat gabungan surat, membukanya di pengolah kata, dan nama-nama non-ASCII telah runtuh menjadi barisan tanda tanya. Datanya tidak pernah berubah. Yang berubah adalah kontrak pengkodean dari format yang Anda tulis, dan setiap jalur ekspor membawa kontrak yang berbeda

Ini adalah jebakan yang menangkap pustaka yang tampak sepenuhnya sadar-Unicode di permukaannya. Teks sel ditahan secara internal sebagai WideString, sehingga modelnya tidak pernah kehilangan satu karakter pun. Kehilangan terjadi di perbatasan, pada penulis yang harus menserialkan teks tersebut ke dalam format dengan aturannya sendiri tentang byte mana yang legal dan bagaimana sesuatu di luar rentang legal harus dikodekan. Benarkan satu penulis dan Anda masih dapat mengirim penulis lain yang mengacaukan teks yang sama. Perbaikannya bukanlah sakelar global. Itu adalah keputusan terpisah yang benar pada setiap jalurnya

RTF adalah format yang aman-7-bit berdasarkan desainnya

Rich Text Format mendahului Unicode dan dispesifikasikan untuk bertahan pada transportasi yang hanya melewatkan ASCII yang dapat dicetak. Dokumen RTF mendeklarasikan halaman kode di tajuknya, dan karakter apa pun yang tidak dapat diwakili oleh penulis di halaman kode tersebut harus dipancarkan sebagai pelarian (escape) daripada sebagai byte mentah. Pelarian yang relevan adalah \u, yang membawa unit kode 16-bit bertanda yang diikuti dengan karakter ASCII pengganti untuk pembaca yang terlalu tua untuk memahami pelarian tersebut sama sekali

HotXLS menulis RTF dengan cara ini. Tajuk dokumen dibuka dengan mendeklarasikan halaman kode, dalam bentuk \ansi\ansicpg1252\uc1, dan penulis di unit lxRTF menelusuri setiap string dengan memancarkan karakter apa pun di atas ASCII biasa sebagai pelarian \u sehingga aliran byte tetap bersih-7-bit terlepas dari apa yang dapat ditampung oleh halaman kode yang dideklarasikan. Titik kode seperti U+4E2D menjadi barisan literal \u20013?, bukan byte mentah yang pembaca akan mencoba menginterpretasikannya melalui halaman kode apa pun yang dianggapnya. Tanpa kedisiplinan itu, apa pun di luar halaman kode yang dideklarasikan tidak memiliki representasi byte legal, dan penulis yang memancarkan nilai mentahnya menghasilkan tanda tanya yang memulai artikel ini

Detail yang perlu diingat adalah bahwa halaman kode yang dideklarasikan dan pelariannya adalah dua bagian dari satu kontrak. Mendeklarasikan halaman kode saja tidak membantu teks yang berada di luarnya. Memancarkan pelarian tanpa halaman kode yang dideklarasikan membiarkan karakter penggantinya ambigu. Keduanya harus benar secara bersamaan, itulah sebabnya penulis yang menangani hanya salah satunya tetap gagal pada buku kerja multibahasa pertamanya

Pelarian HTML lebih dari sekadar tanda kurung sudut

Ekspor HTML menghasilkan dokumen multilembar yang bingkai navigasinya membawa nama lembar sebagai teks yang terlihat. Nama-nama tersebut adalah string yang dikontrol pembuat yang dapat memuat karakter apa pun, termasuk karakter signifikan-markah. Lembar yang secara literal bernama Q1 & Q2 <draft> harus mencapai halaman sebagai entitas yang diloloskan, atau kurung sudut akan membuka tag bayangan dan ampersand akan memulai referensi entitas yang tidak pernah dimaksudkan. Ini adalah pelarian HTML biasa, dan melewatkannya pada label bingkai adalah jenis kelalaian yang lolos dari setiap pengujian yang dibangun dari nama lembar yang hanya ASCII

Pertanyaan pengkodean berada satu lapisan di bawahnya. Ketika karakter non-ASCII mendarat dalam konteks yang tidak dijamin untuk disajikan sebagai UTF-8, representasi amannya adalah referensi karakter numerik, jadi U+00E9 ditulis sebagai &#233; daripada sebagai byte mentah yang maknanya bergantung pada set karakter (charset) respons. Citra cermin dari aturan ini berlaku pada saat masuk. Buku kerja yang dibaca kembali dari XLSX membawa string bersama (shared strings) yang di dalamnya karakter mungkin sudah disimpan sebagai entitas XML numerik, dan entitas tersebut harus didekode ke dalam satu karakter utuh sebelum ia memasuki model sel. Dekode dengan sembarangan, dengan membelah titik kode menjadi byte yang terpisah, dan satu karakter muncul kembali sebagai dua keping mojibake yang tidak dapat diperbaiki oleh ekspor apa pun selanjutnya

Wadah XLSX adalah sebuah ZIP, dan ZIP memiliki pengkodean namanya sendiri

File XLSX adalah sebuah arsip ZIP, dan arsip tersebut menyimpan nama untuk setiap anggota yang dipegangnya. ZIP cukup tua sehingga spesifikasi aslinya tidak mengatakan apa pun tentang pengkodean nama-nama tersebut, jadi pembaca yang tidak menemukan sinyal akan berasumsi halaman kode lokal arsipnya. Asumsi tersebut salah pada saat nama anggota memuat karakter non-ASCII, yang mana ini terjadi dengan nama bagian lembar kerja yang dilokalkan dan dengan media tertanam yang nama file-nya membawa aksen atau aksara non-Latin

Perbaikannya adalah satu bit tunggal. Bit tujuan-umum 11 di setiap tajuk file lokal mendeklarasikan bahwa nama anggota dikodekan sebagai UTF-8. HotXLS memeriksa persis bit tersebut saat ia membaca arsip, menguji tanda (flags) tujuan-umum terhadap topeng (mask) $0800, dan pembaca atau penulis yang mengabaikannya akan salah membaca nama yang implementasi yang benar menyimpannya sebagai UTF-8. Bit ini murah untuk diatur dan murah untuk dihormati, dan ini adalah keseluruhan perbedaan antara nama anggota yang bertahan melalui perjalanan pulang-pergi dan nama yang tiba dalam keadaan rusak sebelum konten lembar kerja itu sendiri selesai diurai

Lipatan besar-kecil-huruf (Case folding) dan pemindaian angka menyembunyikan bahaya yang sama

Evaluasi formula adalah di mana keamanan Unicode berhenti berbicara tentang serialisasi dan mulai berbicara tentang perbandingan. Fungsi SEARCH tidak peka huruf-besar-kecil, yang artinya ia harus melipat huruf (fold case) sebelum mencari sub-string. Cara yang salah untuk melipat adalah melalui halaman kode ANSI, karena mengubah teks non-ASCII menjadi huruf besar dengan cara tersebut akan merutekan karakter melalui halaman kode sempit dan mengacaukan apa pun di luarnya. Cara yang benar adalah pengubahan huruf besar wide-string, yang melestarikan rentang utuh UTF-16. HotXLS melipat dengan WideUpperCase untuk alasan persis seperti ini, jadi pencarian untuk teks beraksen atau non-Latin cocok dengan karakter yang sama dengan yang diberikan daripada perkiraannya yang dikacaukan oleh halaman kode

Pemecah token (tokenizer) formula membawa kewajiban terkait yang sama sekali tidak ada hubungannya dengan huruf dan segalanya tentang di mana sebuah token berakhir. Notasi ilmiah seperti 1E3 atau 2.5E-3 adalah sebuah literal numerik tunggal, dan pemindai harus mengenali E, tanda opsional, dan digit-digit yang mengikutinya sebagai bagian dari angka daripada memutus masukan menjadi nama yang diikuti oleh angka terpisah. Pemindai yang salah menangani hal ini mengubah konstanta yang valid secara sempurna menjadi kesalahan urai (parse error) atau, lebih buruk lagi, sebuah ekspresi yang salah secara diam-diam. Hal ini ada di diskusi yang sama karena kedua kasus tersebut adalah tentang seorang pembaca yang membuat keputusan tingkat-karakter yang benar: satu tentang bagaimana melipat karakter untuk perbandingan, yang lainnya tentang apakah suatu karakter melanjutkan token saat ini

Membangun dan mengekspor buku kerja multibahasa

API publik tidak meminta Anda untuk memikirkan salah satu pun dari hal-hal ini. Anda membangun buku kerja dari nilai sel WideString dan memanggil titik masuk ekspor yang Anda inginkan. Keputusan pengkodean terjadi di dalam masing-masing penulis. Contoh di bawah ini membubuhi sebuah lembar dengan teks dalam berbagai aksara, lalu menulis sebuah file RTF dan sebuah file HTML dari buku kerja yang sama, jadi kedua jalur berjalan berhadapan dengan masukan yang identik

uses
  lxHandle;

procedure ExportMultilingualWorkbook;
var
  Book: IXLSWorkbook;
  Sheet: IXLSWorksheet;
begin
  Book := TXLSWorkbook.Create;
  try
    Sheet := Book.Sheets.Add('Customers');

    Sheet.Cells[1, 1].Value := 'Name';
    Sheet.Cells[1, 2].Value := 'City';

    // Teks sel ditahan sebagai WideString, sehingga setiap aksara bertahan di model.
    Sheet.Cells[2, 1].Value := '王伟';          // Mandarin
    Sheet.Cells[2, 2].Value := '北京';
    Sheet.Cells[3, 1].Value := 'Müller';        // Umlaut Jerman
    Sheet.Cells[3, 2].Value := 'Köln';
    Sheet.Cells[4, 1].Value := 'Иванов';        // Sirilik
    Sheet.Cells[4, 2].Value := 'Москва';
    Sheet.Cells[5, 1].Value := 'Désirée';       // Aksen Prancis
    Sheet.Cells[5, 2].Value := 'Montréal';

    // RTF: penulis lxRTF mendeklarasikan halaman kode dan memancarkan setiap
    // karakter non-ASCII sebagai pelarian \u, menjaga file agar bersih 7-bit.
    Book.SaveAsRTF('Customers.rtf');

    // HTML: nama-nama lembar diloloskan-HTML dan teks non-ASCII ditulis
    // sedemikian rupa sehingga tidak bergantung pada tebakan set karakter respons.
    Book.SaveAsHTML('Customers.html');
  finally
    Book := nil;
  end;
end;

Kedua panggilan mengembalikan sebuah status Integer, dan keduanya mengonsumsi teks dalam-memori yang sama. Tidak ada dalam kode pemanggil yang mendeklarasikan halaman kode atau meloloskan suatu karakter, karena tanggung jawab tersebut berada pada sang penulis yang mengetahui formatnya sendiri. SaveAsCSV pada tingkat-buku-kerja mengikuti bentuk yang sama jika Anda membutuhkan sebuah ekspor berpemisah (delimited) dari sumber yang identik

// Buku kerja yang sama, jalur ekspor ketiga dengan aturan pengkodeannya sendiri.
Book.SaveAsCSV('Customers.csv');

Keamanan Unicode itu per-jalur, bukan per-pustaka

Pelajaran yang layak dibawa pulang adalah bahwa tidak ada satu tempat tunggal untuk menjadi aman-Unicode. RTF membutuhkan halaman kode yang dideklarasikan plus pelarian \u. HTML membutuhkan pelarian entitas untuk karakter signifikan-markah dan referensi numerik di mana set karakternya tidak dijamin, plus dekode yang benar untuk entitas yang tiba di string bersama. Wadah ZIP membutuhkan bit 11 tujuan-umum untuk diatur sehingga nama anggota UTF-8 dibaca sebagai UTF-8. Evaluasi formula membutuhkan pelipatan-huruf wide-string dan pemecah token yang menjaga notasi ilmiah tetap menyatu. Masing-masing dari ini adalah kontrak yang berbeda, dan suatu pustaka dapat memenuhi satu sambil secara diam-diam melanggar yang lainnya. Itulah alasan mengapa alat yang mendapatkan CSV dengan benar masih bisa menyerahkan kepada Anda sebuah RTF yang penuh dengan tanda tanya

Jika ekspor Anda bersandar pada format-format berpemisah, timbal balik di antara mereka tercakup dalam panduan menyeluruh kami mengenai ekspor CSV, TSV, dan HTML, dan ketika sumbernya adalah sebuah himpunan hasil alih-alih lembar yang dibangun manual, pola-pola pada ekspor basis data untuk laporan Delphi berpasangan secara alami dengan aturan-aturan pengkodean yang dijelaskan di sini. Semua ini dikirimkan sebagai bagian dari Komponen HotXLS untuk Delphi dan C++Builder, berdampingan dengan API pembacaan, formula, dan pemformatan yang diliput di tempat lain pada blog ini