Dosyalar dersini ikiye ayıracağım: iki File'ın anlatıldığı birinci bölüm, ve EndianStream'in anlatıldığı ikinci bölüm... Burada kafamı toparlamaya çalışacağım... :)
Önce konuyla ilgili kavramları hatırlatmak istiyorum:
BOM (byte order mark) dosyanın hangi Unicode kodlaması kullandığını belirtiyor.
Endian harfi oluşturan baytların dosyaya hangi sırada yazıldıklarını belirtiyor. Örneğin "ğüşiöçıĞÜŞİÖÇI" dizgisi bir dosyaya UTF16BE BOM belirteciyle, BigEndian bayt sırasıyla, ve writeLineW() ile yazılmışsa:
'00000000: feff 011f 00fc 015f 0069 00f6 00e7 0131 ......._.i.....1
00000010: 011e 00dc 015e 0130 00d6 00c7 0049 000a .....^.0.....I..'
Aynı dizgi, bu sefer UTF16LE BOM belirteciyle, LittleEndian bayt sırasıyla, ve yine writeLineW() ile yazılmışsa:
'00000000: fffe 1f01 fc00 5f01 6900 f600 e700 3101 ......_.i.....1.
00000010: 1e01 dc00 5e01 3001 d600 c700 4900 0a00 ....^.0.....I...'
En başlardaki 011e ve 1e01, BOM'un bayt sırasına göre farklı yazılışını gösteriyor... Ondan sonraki iki bayt dizginin başındaki 'ğ'ye karşılık geliyor. 'ğ'nin Unicode kodu 011f'tir. Bu değeri oluşturan 01 ve 1f baytlarının iki değişik bayt sırasında nasıl farklı yazıldıklarını görüyorsunuz.
"BOM barındırmama"yı da sayarsak elimizde 6 değişik BOM değeri var: bomsuzluk, UTF8, UTF16LE, UTF16BE, UTF32LE, UTF32BE.
EndianStream'in bayt sırasına bizim karışmamamızı da sayarsak 3 değişik bayt sırası var: baytSırasız, Endian.LittleEndian, Endian.BigEndian.
Dosyaya yazmak için de 3 değişik yazma işlevi:
-
writefln ve arkadaşları: eğer doğru anlıyorsam, bunlar EndianStream'in bayt sırasından bağımsız olarak hep kendi içeriklerini yazdırıyorlar.
-
writeLine (writeString ile aynı ama satır sonu da yazdırıyor): bunlar yalnızca char yazıyorlar (?)
-
writeLineW (writeStringW ile yanı ama satır sonu da yazdırıyor): wchar (16 bit) yazan işlev; bunların dchar (32 bit) yazanı yok; belki unutulmuştur, belki de 32 bitlik Unicode kodlamaları yaygın olmadığı için önem verilmemiştir (?)
Bu seçenekler 6x3x3 = 54 karışım oluşturuyor. Bu karışımlardan bazıları aynı D programında bile okunamıyorlar. O karışımları oluşturan dosyalar oluşturdum ve aynı D programında açılamayanları gözardı ettim. Bunlar da tutarsızlık var.
UTF8 BigEndian writeLineW
UTF16LE baytSırasız writefln
UTF16LE baytSırasız writeLine
UTF16LE BigEndian writefln
UTF16LE BigEndian writeLine
UTF16LE BigEndian writeLineW
UTF16LE LittleEndian writefln
UTF16LE LittleEndian writeLine
UTF16BE baytSırasız writeLineW
UTF16BE LittleEndian writeLineW
UTF32LE BigEndian writeLineW
UTF32BE baytSırasız writeLineW
UTF32BE LittleEndian writeLineW
Örneğin UTF32BE olduğu halde akımı LittleEndian olarak ayarlamak yanlış oluyor. Benzer şekilde, readLineW ile 16 bit olmayan kodlar okumak yanlış oluyor...
Açan program (örneğin metin düzenleyici): Bu karışımları Emacs, gedit, OpenOffice Writer, Microsoft Word, ve Mac'teki metin düzenleyiciyle (ismini hatırlamıyorum) açmayı denedim; çoğu, dosyada BOM olmasa bile kodlamayı anlıyor ve bazen kullanıcıya sorarak doğru açıyor; Emacs 22, UTF8 BOM'unu da karakter gibi (kutu karakteri gibi) gösteriyor, UTF8 BOM desteği 23'üncü sürümde gelecekmiş, ama 16 bitlik BOM'larla sorunu yok
Microsoft dünyasında ismi "Unicode" olarak geçen kodlama son derece yanıltıcı. Çünkü onun yanında listeledikleri UTF-8'in de bir Unicode kodlaması olduğunu biliyoruz. Microsoft'un "Unicode" dediği kodlama, aslında 16 bitliklerden birisi. (Hangisi olduğunu hatırlamıyorum.)
Denediğim karışımlardan bazıları aşağıda. 'X' olanlar harflerin yanlış çıktıklarını gösteriyor. "(bom)" olanlar, BOM karakterinin de kutu veya başka bir şekilde çıktığını gösteriyor.
'
bomsuz writefln emacs gedit oow (denemeyi unuttum)
bomsuz writeLine emacs gedit oow word notepad X
bomsuz writeLineW X gedit oow word notepad X
UTF8 writefln emacs(bom) gedit oow word notepad mac
UTF8 writeLine emacs(bom) gedit oow word notepad mac
UTF8 writeLineW X X X X X X
UTF16BE writefln X X X
UTF16BE writeLine X X X
UTF16LE writeLineW emacs gedit oow word notepad mac
UTF32BE writefln X X X
UTF32BE writeLine X X X
UTF32LE writefln X X X
UTF32LE writeLine X X X
UTF32LE writeLineW emacs(bom) X oow(bom)
'
Bütün bunların ışığında daha tam emin olmadığım :) şu kuralları geliştirdim:
File kuralları
- BOMsuz ASCII ve BOMsuz UTF8 dosyalarla kullanın
Not: Erdem, senin yaşadığın sorun sanırım dosyanın 16 bitlik bir kodlama kullanması; örneğin Notepad'de Unicode ile kaydetmiş olabilirsin. Onu readLineW() işlevi ile okumak gerekiyor.
EndianStream Kuralları
Yazarken:
-
Bayt sırasına (Endian) dokunmayın; olsa olsa tutarsızlığa neden olunuyor
-
32 bitlik kodlamayı unutun; metin düzenleyiciler ve Phobos desteklemiyor (zaten çok kayıplı bir kodlama)
-
herhangi bir nedenle 16 bitlik kodlama kullanacaksanız writeLineW() işlevini kullanın
-
UTF8 yeğleyin ama BOM yazmasanız da çoğu metin düzenleyici ve Phobos doğru okur
-
Doğrucu olmak için writeBOM()'u kullanın; Emacs de nasıl olsa sonunda düzeltilecek
Okurken:
-
Bayt sırasına (Endian) dokunmayın; olsa olsa tutarsızlığa neden olunuyor
-
EndianStream ile okurken mutlaka readBOM()'u çağırın; akım, bayt sırasını öyle öğreniyor
-
Eğer readBOM() -1 döndürürse, yani dosyada BOM yoksa, yapacak bir şey yok. Zaten bu yüzden çoğu metin düzenleyici kodlamayı öğrenmek için kullanıcıya bir menü gösteriyor. Siz de öyle yapabilir ve onların seçimine göre readString+readLine veya readStringW+readLineW işlev çiftlerinden hangisini kullanacağınıza karar verebilirsiniz. Eğer readBOM() 16 bitlik bir kodlama döndürürse, dosyayı readStringW ve readLineW işlevleriyle okuyun.
Ali
Not: Bütün bunları öğrenmek için yazdığım program aşağıda. Siz de kendi ortamınızda deneyebilirsiniz.
import std.conv;
import std.stream;
import std.cstream;
import std.system;
import std.file;
/// BOM kullanmamayı ifade eden özel BOM değeri
immutable BOM BOMsuzluk = cast(BOM)-1;
/// Dosyanın bayt sırası ayarını değiştirmemeyi ifade eden
/// özel Endian değeri
immutable Endian baytSırasıOlmamak = cast(Endian)-1;
/**
* Bir enum'un bütün değerlerini içeren dizi oluşturur
*
* Returns: Oluşturulan dizi
*/
T[] enumDizisi(T)()
{
T[] dizi;
foreach(değer; T.min..T.max) {
dizi ~= değer;
}
dizi ~= T.max;
return dizi;
}
/**
* Bir enum'un bütün değerlerini ve hepsinden önceki özel bir
* değeri içeren dizi oluşturur
*
* Returns: Oluşturulan dizi
*/
T[] enumDizisiVeBirEk(T)(T ekDeğer)
{
T[] dizi = [ ekDeğer ];
return dizi ~= enumDizisi!T;
}
/**
* Programda kullanılan bütün BOM değerleri
*/
BOM[] bütünBOMlar()
{
return enumDizisiVeBirEk!BOM(BOMsuzluk);
}
/**
* Programda kullanılan bütün bayt sırası değerleri
*/
Endian[] bütünBaytSıraları()
{
return enumDizisiVeBirEk!Endian(baytSırasıOlmamak);
}
/// Yazma işlevi türü
typedef void function(Stream) Yazmaİşlevi;
/// Okuma işlevi türü
typedef Yazmaİşlevi Okumaİşlevi;
/// Denenen bütün yazma işlevleri
Yazmaİşlevi[] bütünYazmaİşlevleri()
{
return [ &writeflnİleYaz, &writeLineİleYaz, &writeLineWİleYaz ];
}
/// Denenen bütün okuma işlevleri
Okumaİşlevi[] bütünOkumaİşlevleri()
{
return [ &readfİleOku, &readLineİleOku, &readLineWİleOku ];
}
/// Bu biraz garip: okuma için de yazma için kullanılan
/// etiketten yararlanılıyor; çünkü dosyanın isminde yazma
/// işlevi yer alıyor
string[Yazmaİşlevi] işlevEtiketleri()
{
string[Yazmaİşlevi] etiketler;
etiketler[&writeflnİleYaz] = "writefln";
etiketler[&readfİleOku] = "writefln";
etiketler[&writeLineİleYaz] = "writeLine";
etiketler[&readLineİleOku] = "writeLine";
etiketler[&writeLineWİleYaz] = "writeLineW";
etiketler[&readLineWİleOku] = "writeLineW";
return etiketler;
}
/// Verilen işleve karşılık gelen etiket
string işlevEtiketi(Yazmaİşlevi işlev)
{
return işlevEtiketleri[işlev];
}
void main()
{
dosyalarıOluştur();
dosyalarıOku();
}
void dosyalarıOluştur()
{
foreach(bom; bütünBOMlar) {
foreach(baytSırası; bütünBaytSıraları) {
foreach(işlev; bütünYazmaİşlevleri) {
try {
dout.writefln("Yazılan: ",
BOMetiketi(bom), ' ',
baytSırasıEtiketi(baytSırası), ' ',
işlevEtiketi(işlev));
işlev(yazıcıAkımYap(bom, baytSırası, işlev));
} catch (Exception hata) {
dout.writefln("HATA: Bu karışım yazılamıyor");
remove(dosyaİsmiYap(bom, baytSırası, işlev));
}
}
}
}
}
void dosyalarıOku()
{
foreach(bom; bütünBOMlar) {
foreach(baytSırası; bütünBaytSıraları) {
foreach(işlev; bütünOkumaİşlevleri) {
try {
dout.writefln("Okunan : ",
BOMetiketi(bom), ' ',
baytSırasıEtiketi(baytSırası), ' ',
işlevEtiketi(işlev));
Stream akım = okuyucuAkımYap(bom, baytSırası, işlev);
if (akım.isOpen) {
işlev(akım);
} else {
dout.writefln("dosya açılamadı");
}
} catch (Exception hata) {
dout.writefln("HATA: Bu karışım okunamıyor");
remove(dosyaİsmiYap(bom, baytSırası, işlev));
}
}
}
}
}
string BOMetiketi(BOM bom)
{
return (bom == BOMsuzluk
? "bomsuz"
: to!string(bom));
}
string baytSırasıEtiketi(Endian baytSırası)
{
return (baytSırası == baytSırasıOlmamak
? "baytSırasız"
: to!string(baytSırası));
}
string denemeEtiketi(BOM bom, Endian baytSırası, Yazmaİşlevi işlev)
{
return
BOMetiketi(bom) ~ '_'
~ baytSırasıEtiketi(baytSırası) ~ '_'
~ işlevEtiketi(işlev);
}
string dosyaİsmiYap(BOM bom, Endian baytSırası, Yazmaİşlevi işlev)
{
return "unicode_deneme_" ~ denemeEtiketi(bom, baytSırası, işlev);
}
EndianStream EndianStreamYap(File dosya, Endian baytSırası)
{
// Bayt sırasını belirleyip belirlememe kararı burada veriliyor
return
(baytSırası == baytSırasıOlmamak)
? new EndianStream(dosya)
: new EndianStream(dosya, baytSırası);
}
Stream yazıcıAkımYap(BOM bom, Endian baytSırası, Yazmaİşlevi işlev)
{
string dosyaİsmi = dosyaİsmiYap(bom, baytSırası, işlev);
auto dosya = EndianStreamYap(new File(dosyaİsmi, FileMode.OutNew),
baytSırası);
// BOM'u yazıp yazmamak burada belirleniyor
if (bom != BOMsuzluk) {
dosya.writeBOM(bom);
}
return dosya;
}
Stream okuyucuAkımYap(BOM bom, Endian baytSırası, Yazmaİşlevi işlev)
{
string dosyaİsmi = dosyaİsmiYap(bom, baytSırası, işlev);
auto dosya = EndianStreamYap(new File(dosyaİsmi, FileMode.In), baytSırası);
// BOM'u okuyup okumamak burada belirleniyor. Aslında
// readBOM() her dosya için çağrılabilir: eğer BOM yoksa
// akımdan hiç okunmamış gibi -1 döndürüyor.
if (bom != BOMsuzluk) {
dosya.readBOM();
}
return dosya;
}
/// Dizginin uzunluğu ve içeriği
string dizgiBilgisi(T) (T dizgi)
{
return to!string(dizgi.length) ~ " baytlık '" ~ to!string(dizgi) ~ "'";
}
/// Dizgileri denetler
void eşitOlmalılar(T)(T[] beklenen, T[] asıl)
{
if (beklenen != asıl) {
throw new Exception("Eşit değiller"
~ "; beklenen: " ~ dizgiBilgisi(beklenen)
~ "; asıl: " ~ dizgiBilgisi(asıl));
}
}
void writeflnİleYaz(Stream dosya)
{
dosya.writefln("ğüşiöçıĞÜŞİÖÇI"c);
dosya.writefln("ğüşiöçıĞÜŞİÖÇI"w);
dosya.writefln("ğüşiöçıĞÜŞİÖÇI"d);
}
void readfİleSatırDene(T)(Stream dosya, T[] beklenen)
{
T[] satır;
dosya.readf(&satır);
eşitOlmalılar!T(beklenen, satır);
}
void readfİleOku(Stream dosya)
{
readfİleSatırDene!char(dosya, "ğüşiöçıĞÜŞİÖÇI"c.dup);
readfİleSatırDene!wchar(dosya, "ğüşiöçıĞÜŞİÖÇI"w.dup);
readfİleSatırDene!dchar(dosya, "ğüşiöçıĞÜŞİÖÇI"d.dup);
}
void writeLineİleYaz(Stream dosya)
{
dosya.writeLine("ğüşiöçıĞÜŞİÖÇI"c.dup);
}
void readLineİleOku(Stream dosya)
{
eşitOlmalılar!char(dosya.readLine(), "ğüşiöçıĞÜŞİÖÇI"c.dup);
}
void writeLineWİleYaz(Stream dosya)
{
dosya.writeLineW("ğüşiöçıĞÜŞİÖÇI"w.dup);
}
void readLineWİleOku(Stream dosya)
{
eşitOlmalılar!wchar(dosya.readLineW(), "ğüşiöçıĞÜŞİÖÇI"w.dup);
}
--
[ Bu gönderi, http://ddili.org/forum'dan dönüştürülmüştür. ]