Bir elektronik tablo, müşteri adlarından oluşan bir sütun tutar. Bazıları Çince, bazıları Kiril alfabesiyle yazılmıştır, birkaçı Almanca umlaut veya Fransızca aksan taşır. Bunu CSV'ye aktarır ve sonucu açarsınız; her karakter bozulmamıştır. Aynı çalışma kitabını bir adres mektup birleştirme şablonu için RTF'ye aktarır, bir kelime işlemcide açarsınız ve ASCII olmayan isimler soru işaretlerinden oluşan satırlara dönüşmüştür. Veri asla değişmedi. Değişen şey, yazdığınız biçimin kodlama sözleşmesidir ve her dışa aktarım yolu farklı bir sözleşme taşır.
This is the trap that catches a library which looks fully Unicode-aware on the surface. Cell text is held internally as WideString, so the model never loses a character. The loss happens at the boundary, in the writer that has to serialise that text into a format with its own rules about which bytes are legal and how anything outside the legal range must be encoded. Get one writer right and you can still ship another that mangles the same text. The fix is not a global switch. It is a separate, correct decision on every path.
RTF tasarım gereği 7 bit güvenli bir biçimdir
Zengin Metin Biçimi (Rich Text Format - RTF) Unicode'dan öncesine dayanır ve yalnızca yazdırılabilir ASCII geçiren taşımalardan sağ çıkacak şekilde belirtilmiştir. Bir RTF belgesi başlığında bir kod sayfası bildirir ve yazarın o kod sayfasında temsil edemediği herhangi bir karakter, ham bir bayt yerine bir çıkış (escape) karakteri olarak verilmelidir. İlgili çıkış karakteri, çıkışı hiç anlamayacak kadar eski okuyucular için bir ASCII yedek karakteri tarafından takip edilen işaretli bir 16 bitlik kod birimi taşıyan \u karakteridir.
HotXLS, RTF'yi bu şekilde yazar. Belge başlığı, \ansi\ansicpg1252\uc1 formunda kod sayfasını bildirerek açılır ve lxRTF birimindeki yazar, düz ASCII üzerindeki her karakteri bir \u çıkışı olarak vererek her dizeyi yürütür; böylece bayt akışı bildirilen kod sayfasının ne tutabileceğine bakılmaksızın 7 bit temiz kalır. U+4E2D gibi bir kod noktası, bir izleyicinin daha sonra varsaydığı herhangi bir kod sayfası aracılığıyla yorumlamaya çalışacağı ham bir bayt değil, tam olarak \u20013? dizisi haline gelir. Bu disiplin olmasaydı, bildirilen kod sayfasının dışındaki hiçbir şeyin yasal bir bayt temsili olmazdı ve ham değeri veren bir yazar bu makaleyi başlatan soru işaretlerini üretirdi。
Unutulmaması gereken ayrıntı, bildirilen kod sayfası ile çıkış karakterlerinin tek bir sözleşmenin iki yarısı olduğudur. Yalnızca kod sayfasını bildirmek, onun dışındaki metne yardımcı olmaz. Bildirilen bir kod sayfası olmadan çıkış karakteri vermek yedek karakterleri belirsiz bırakır. Her ikisinin de birlikte doğru olması gerekir; bu nedenle bunlardan yalnızca birini işleyen bir yazar, ilk çok dilli çalışma kitabında yine de başarısız olur.
HTML kaçışı açılı ayraçlardan daha fazlasıdır
HTML dışa aktarımı, gezinme çerçeveleri sayfa adlarını görünür metin olarak taşıyan çok sayfalı bir belge üretir. Bu adlar, işaretleme açısından önemli olanlar da dahil olmak üzere herhangi bir karakteri içerebilen, yazar tarafından kontrol edilen dizelerdir. Kelimenin tam anlamıyla Q1 & Q2 <draft> olarak adlandırılan bir sayfa, sayfaya kaçışlı varlıklar (entities) olarak ulaşmalıdır, aksi takdirde açılı ayraçlar hayali bir etiket açar ve ampersand asla amaçlanmayan bir varlık referansı başlatır. Bu sıradan HTML kaçışıdır ve bir çerçeve etiketinde bunu atlamak, yalnızca ASCII içeren sayfa adlarından oluşturulan her testi geçen türden bir ihmaldir。
Kodlama sorusu bunun bir katman altında oturur. ASCII olmayan karakterler UTF-8 olarak sunulması garanti edilmeyen bir bağlama düştüğünde, güvenli temsil sayısal bir karakter referansıdır; bu nedenle U+00E9, anlamı yanıt karakter kümesine (charset) bağlı olan ham bir bayt yerine é olarak yazılır. Bu kuralın ayna görüntüsü içeri girerken geçerlidir. XLSX'ten geri okunan bir çalışma kitabı, bir karakterin zaten sayısal bir XML varlığı olarak depolanmış olabileceği paylaşılan dizeleri taşır ve bu varlığın hücre modeline girmeden önce tek bir bütün karakter olarak kodu çözülmelidir. Bunu dikkatsizce çözüp bir kod noktasını ayrı baytlara ayırırsanız, tek bir karakter daha sonraki hiçbir dışa aktarımın onaramayacağı iki mojibake parçası olarak yeniden ortaya çıkar。
XLSX kabı bir ZIP'tir ve ZIP'in kendi ad kodlaması vardır
Bir XLSX dosyası bir ZIP arşividir ve arşiv tuttuğu her üye için bir ad depolar. ZIP, orijinal belirtiminin bu adların kodlanması hakkında hiçbir şey söylemeyeceği kadar eskidir, bu nedenle hiçbir sinyal bulamayan bir okuyucu arşivin yerel kod sayfasını varsayar. Bu varsayım, bir üye adının ASCII olmayan bir karakter içerdiği an yanlıştır; bu durum yerelleştirilmiş çalışma sayfası bölüm adlarında ve dosya adları aksan veya Latin dışı yazı taşıyan gömülü medyalarda meydana gelir。
Çözüm tek bir bittir. Her yerel dosya başlığındaki genel amaçlı bit 11, üye adının UTF-8 olarak kodlandığını bildirir. HotXLS bir arşivi okurken tam olarak bu biti kontrol eder, genel amaçlı bayrakları $0800 maskesine göre test eder ve bunu göz ardı eden bir okuyucu veya yazar, doğru bir uygulamanın UTF-8 olarak depoladığı adı yanlış okuyacaktır. Biti ayarlamak ucuzdur ve uymak ucuzdur ve gidiş-dönüş yolculuğundan sağ kurtulan bir üye adı ile e-tablo içeriği ayrıştırılmadan önce bozuk gelen bir üye adı arasındaki tüm farktır。
Büyük/küçük harf dönüştürme ve sayı tarama aynı tehlikeyi gizler
Formül değerlendirme, Unicode güvenliğinin serileştirme ile ilgili olmaktan çıkıp karşılaştırma ile ilgili hale geldiği yerdir. SEARCH fonksiyonu büyük/küçük harfe duyarsızdır, bu da bir alt dizeyi aramadan önce harf durumunu katlaması gerektiği anlamına gelir. Büyük harfe dönüştürmenin yanlış yolu ANSI kod sayfası üzerindendir, çünkü ASCII olmayan metni bu şekilde büyük harfle yazmak karakterleri dar bir kod sayfasından yönlendirir ve dışındaki her şeyi bozar. Doğru yol, tam UTF-16 aralığını koruyan geniş dize (wide-string) büyük harfe dönüştürmedir. HotXLS tam olarak bu nedenle WideUpperCase ile katlar, böylece aksanlı veya Latin dışı metin araması, kod sayfasıyla bozulmuş yaklaşık değerleri yerine kendisine verilen aynı karakterlerle eşleşir。
Formül belirteçleştiricisi (tokenizer), harflerle hiçbir ilgisi olmayan ve bir belirtecin nerede bittiğiyle tamamen ilgisi olan ilgili bir yükümlülük taşır. Bilimsel gösterimler tek bir sayısal sabittir ve tarayıcının, girdiyi bir ada ve ardından ayrı bir sayıya bölmek yerine E'yi, isteğe bağlı işareti ve sonraki basamakları sayının bir parçası olarak tanıması gerekir. Bunu yanlış işleyen bir tarayıcı, tamamen geçerli bir sabiti ayrıştırma hatasına veya daha kötüsü sessizce yanlış bir ifadeye dönüştürür. Aynı tartışmaya aittir çünkü her iki durum da okuyucunun doğru karakter düzeyinde karar vermesiyle ilgilidir: biri karşılaştırma için bir karakterin nasıl katlanacağı, diğeri ise bir karakterin mevcut belirteci devam ettirip ettirmediği hakkındadır。
Çok dilli bir çalışma kitabı oluşturma ve dışa aktarma
Genel API sizi bunların hiçbiri hakkında düşünmeye zorlamaz. Çalışma kitabını WideString hücre değerlerinden oluşturur ve istediğiniz dışa aktarım giriş noktasını çağırırsınız. Kodlama kararları her yazarın içinde gerçekleşir. Aşağıdaki örnek, bir sayfayı birkaç yazı sistemindeki metinle doldurur, ardından aynı çalışma kitabından hem bir RTF dosyası hem de bir HTML dosyası yazar; böylece iki yol aynı girdiye karşı çalışır。
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';
// Cell text is held as WideString, so every script survives the model.
Sheet.Cells[2, 1].Value := '王伟'; // Chinese
Sheet.Cells[2, 2].Value := '北京';
Sheet.Cells[3, 1].Value := 'Müller'; // German umlaut
Sheet.Cells[3, 2].Value := 'Köln';
Sheet.Cells[4, 1].Value := 'Иванов'; // Cyrillic
Sheet.Cells[4, 2].Value := 'Москва';
Sheet.Cells[5, 1].Value := 'Désirée'; // French accents
Sheet.Cells[5, 2].Value := 'Montréal';
// RTF: the lxRTF writer declares the code page and emits every
// non-ASCII character as a \u escape, keeping the file 7-bit clean.
Book.SaveAsRTF('Customers.rtf');
// HTML: sheet names are HTML-escaped and non-ASCII text is written
// so it does not depend on a guessed response charset.
Book.SaveAsHTML('Customers.html');
finally
Book := nil;
end;
end;
Her iki çağrı da Integer durumu döndürür ve her ikisi de aynı bellek içi metni tüketir. Çağıran koddaki hiçbir şey bir kod sayfası bildirmez veya bir karakterden kaçmaz, çünkü sorumluluk kendi biçimini bilen yazardadır. Çalışma kitabı düzeyindeki SaveAsCSV, aynı kaynaktan sınırlı bir dışa aktarmaya ihtiyacınız varsa aynı şekli izler。
// Same workbook, a third export path with its own encoding rules.
Book.SaveAsCSV('Customers.csv');
Unicode güvenliği kitaplık başına değil, yol başınadır
Çıkarılması gereken ders, Unicode açısından güvenli olunacak tek bir yerin olmadığıdır. RTF bildirilen bir kod sayfasına artı \u çıkışlarına ihtiyaç duyar. HTML, işaretleme açısından önemli karakterler için varlık (entity) kaçışına ve karakter kümesinin (charset) garanti edilmediği durumlarda sayısal referanslara, ayrıca paylaşılan dizelerde gelen varlıkların doğru kod çözümüne ihtiyaç duyar. ZIP kabı genel amaçlı bit 11'in ayarlanmasına ihtiyaç duyar, böylece UTF-8 üye adı UTF-8 olarak okunur. Formül değerlendirme, geniş dize büyük/küçük harf dönüştürmesine ve bilimsel gösterimi tek parça halinde tutan bir belirteçleştiriciye ihtiyaç duyar. Bunların her biri farklı bir sözleşmedir ve bir kütüphane birini yerine getirirken diğerini sessizce ihlal edebilir. CSV'yi doğru yapan bir aracın size hala soru işaretleriyle dolu bir RTF sunabilmesinin nedeni budur。
Sınırlı biçimlere dışa aktarma yapıyorsanız, aralarındaki ödünleşimler CSV, TSV ve HTML dışa aktarma kılavuzumuzda ele alınmıştır ve kaynak elle oluşturulmuş bir sayfa yerine bir sonuç kümesi olduğunda, Delphi raporları için veritabanı dışa aktarma kalıpları burada açıklanan kodlama kurallarıyla doğal olarak eşleşir. Tüm bunlar, bu blogun başka yerlerinde ele alınan okuma, formül ve biçimlendirme API'lerinin yanı sıra Delphi ve C++Builder için HotXLS Bileşeninin bir parçası olarak gönderilir。