9 Şubat 2013 Cumartesi

Python ile Web sayfalarının karakter kodlamasının tespit edilmesi

Bir Web sayfasının karakter kodlamasını (character encoding) tespit etmenin her durumda işe yarayan bir çözümü yoktur. Özellikle Türkçe karakterler içeren sayfaların karakter kodlamasının doğru tespit edilmesi sık karşılaşılan bir problemdir.

Karakter kodlamasınının doğru tespit tespit edilmesi için birçok yöntem kullanılabilmesine rağmen şahsen ben genellikle kestirme bir yol kullanıyorum. Aşağıda örnek kodunu verdiğim yöntem özetle sayfanın karakter kodlamasını varsayılan olarak UTF-8 kabul ediyor, eğer UTF-8'den Unicode'a çevirme başarısız olursa karakter kodlamasını windows-1254 olarak kabul ediyor.

Peki neden windows-1254? Sayfanın içeriği aslında ne olursa olsun eğer metin Türkçe değilse yalnızca "ð ý þ Ð Ý Þ" karakteri Türkçe koda tablosundaki "ğ ı ş Ğ İ Ü" harfleri ile aynı kodu kullanmaktadırlar ve Türkçe açısından genellikle bir sıkıntı teşkil etmeyeceklerdir.

12 Kasım 2012 Pazartesi

Python'da Türkçe Karakter Problemi ve Çözümü

Üzgünüm, ideal bir dünyada yaşamıyoruz, Türkçe karakter ya da daha genel bir deyim ile karakter kodlama ve çözme problemleri ile hemen hemen her programlama dilinde karşılaşıyoruz ve Python'da bunun bir istisnası değil. Bu yazıda fazla teknik detaya girmeden Python programlama dilinde karakter kodlamada karşılaşılan problemlere yönelik basit reçeteler vermeyi hedefliyorum. Konuyla ilgili olarak Joel Spolsky'nin "The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)" başlıklı yazısını okumanızı şiddetle tavsiye ediyorum. İngilizcesine yeteri kadar güvenmeyenlere iyi haber, yazı "Kesinlikle En Düşük Seviyeli Yazılım Geliştirici Bile Unicode ve Karakter Setlerini Bilmelidir (Özürsüz !)" başlığı ile Türkçe'ye de çevirilmiş durumda.
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc4 in position
10: ordinal not in range(128)
Yukarıdaki hata mesajı tanıdık geldiyse doğru yerdesiniz. Genel bir kural olarak karakter kodlama (chracter encoding) problemlerinden kaçınmak için;
  • Mümkün olduğu kadar erken metni Unicode'a dönüştürmek (decode)
  • Bütün işlemlerde Unicode kullanmak
  • Unicode metni mümkün olduğu kadar geç herhangi bir karakter kodlamasına dönüştürmek.
Bahsettiğim makaleleri okumaya vaktiniz yoksa ve kısa yoldan çözüme ulaşmaya çalışıyorsanız en azından Unicode nedir bilmeniz gerekiyor.

Peki nedir Unicode?

Bilgisayarlar temel olarak sadece sayılarla işlem yaparlar. Harfleri ve diğer karakterleri her birisine birer sayı atayarak depolarlar. Unicode geliştirilmeden önce çeşitli dillerdeki rakam ve harflere sayı atamak için onlarca farklı kodlama sistemi kullanılmaktaydı. Bu kodlama sistemlerinden hiç birisi mevcut bütün harf, işaret ve rakamları kapsayacak kadar geniş değildi. Ayrıca bu kodlama sistemleri birbirleri ile çakışmakta idi. Örneğin iki farklı kodlama sistemi aynı sayıyı iki farklı karakter kullanabileceği gibi aynı karakter için iki farklı sayı da kullanabilir. Türkçe karakterlerin doğru şekilde görüntülenmemesinin ana nedeni budur.

Günümüzde bilgisayarların aynı anda birden fazla karakter kodlamasını desteklemesi zorunlu hale gelmiştir. Ortaya çıkan bu zorunluluk her karaktere bir sayı değeri karşılığı atayan Unicode standardının geliştirilmesine neden olmuştur. Her bir karakter için Unicode standardı tarafından atanan sayıya o karakterin code point'i denilir ve hiçbir zaman değişmez.

Madem bütün karakterleri eşsiz olarak tanımlamak için bir code point dediğimiz bir sayı var neden hala Türkçe karakter problemi yaşıyoruz? Maalesef bu konuda bir standart olmasına rağmen karakter kodlarının (code point) hafızada nasıl tutulacağı ve diske nasıl yazılacağı konusunda birden fazla standart vardır. Metnin diske yazılmasında kullanılan UTF-8 kodlama standardı en yaygın olanıdır ve her türlü metin dosyasında standart olarak UTF-8 kodlaması kullanmanızı hararetle tavsiye ediyorum.


Nerede kalmıştık...

Ehem, yukarıda bahsettiğimiz kuralı bu bilgiler ışığında söyle yeniden ifade edebiliriz.

  • Mümkün olduğu kadar erken metindeki karakterlerin sayı karşılıklarını oku ve bunları evrensel olarak kabul edilen Unicode sayı karşılığına (code point) çevir.
  • Bütün işlemlerde Unicode sayı karşılığını (code point) kullan
  • İşlemler bitince Unicode sayı karşılığını (code point) herhangi bir karakter kodlamasına, tercihan UTF-8, dönüştür ve diske kaydet veya ağ üzerinden aktar.

"Ğ" yumuşak G

Türçe karakter seti için tanımlanan ISO-8859-9 karakter kodlama tablosunda "Ğ" yumuşak G için 208 sayısı atanmıştır. Latin harfleri için tanımlanan ve genellikle İngilizce metinler için kullanılan ISO-8859-1 karakter kodlama tablosunda 208 sayısı "?" karakteri için kullanılmaktadır. Yani metnin karakter kodlamasını bilmiyorsanız 208 sayısının "Ğ" mi yoksa "Ð" karakterini mi temsil ettiğini anlamak son derece zordur. İşte buna benzer karışıklıkların önüne geçmek için ilk olarak yapılması gereken işlem dosyadan okunan metindeki her bir karakterin Unicode sayı karşılığına (code point) çevrilmesidir. "Ğ" yumuşak G nin Unicode sayı karşılığı (code point) 286 (U+011E) dır. Tablo-1 de bazı Türkçe karakterler için (ğışĞİÜ) ISO-8859-9 kod tablosunda kullanılan numara ve Unicode numarasının farklı olduğunu görebilirsiniz. Dikkatli gözler bu karakterlerin Unicode numaralarının 255 den de büyük olduğunu ve en az iki byte ile ifade edilebileceğini de görecektir.

Tablo -1

Farklı karakter kod tablolarının aynı numarayı farklı karakterleri ifade etmek için nasıl kullandığını Tablo-2 de görebilirsiniz. Tabloyu incelediğinizde doğru karakter kodlama tablosunun kullanılmaması durumunda Türkçe karakterlerden "ğ ı ş Ğ İ Ü" nin sıklıkla neden hatalı olarak "ð ý þ Ð Ý Þ" karakteri şeklinde görüntülendiğini anlayabilirsiniz.

Tablo-2

Yukarıdaki gibi Türkçe karakterler içeren ve ISO-8859-9 kodlama tablosu kullanan bir dosyayı problemsiz olarak aşağıdaki şekilde okuyup Python Unicode metin formatına dönüştürebiliriz.

Türkçe karakterler içeren ve ISO-8859-9 karakter kodlamasını kullanan örnek bir metin dosyası.

Hepsi bu kadar mı? Hayır. Burada yaptığımız işlem herhangi bir karakter kodlamasını kullanan (örnekte iso-8859-9) bir metin dosyasını satır satır okuyup her bir satırı Unicode formatına dönüştürmekten ibaret. Bu aşamadan sonra unicode_metin değikenini sanki normal bir string değişkeniymiş gibi kullanabiliriz. Mesela len(unicode_metin) fonksiyonu Türkçe karakter içeren metinler için de doğru sonuç verecektir.

Peki Unicode formatına dönüştürdüğümüz metin üzerinde yapmak istediğimiz işlemler tamamlandıktan sonra metni tekrar bir dosyaya kaydetmek istersek ne yapmamız gerekiyor? Maalesef Unicode metni aşağıdaki gibi doğrudan dosyaya kaydetmek hata ile sonuçlanacaktır.

Hata mesajı şuna benzer olacaktır.

UnicodeEncodeError: 'ascii' codec can't encode character u'\xfc' in position 9:
ordinal not in range(128)

Hatanın nedeni Python'da varsayılan karakter kodlamasının ASCII olması ve Unicode metinde ASCII karakterler dışında karakterlerin de bulunmasıdır. Bu nedenle Unicode metin diske kaydedilmeden önce arzu edilen karakter kod tablosuna tekrar dönüştürülmeli ve ondan sonra dosyaya yazılmalıdır. Aşağıdaki örnekte ISO-8859-9 karakter kodlamasına sahip bir metin dosyası satır satır okunmakta, her bir satır Unicode formatına dönüştürülmekte, metin üzerinde işlemler tamamlandıktan sonra unicode_metin değişkeni arzu edilen bir karakter kodlamasına (örnekte UTF-8) dönüştürülerek hedef dosyaya kaydedilmektedir.

Türkçe karakterler içeren ve ISO-8859-9 karakter kodlamasını kullanan örnek dosyanın UTF-8 kodlamasına dönüştürülmüş hali.

Burada kullanılan unicode_metin.encode('utf-8') metodunu daha yakından incelememiz gerekiyor. Metod özetle Unicode metni istenilen kod tablosuna çevirmektedir. Karakter kod tabloları birbirlerinden farklı karakterler içerdiklerinden aralarında yapılan dönüşümlerde bazı karakterlerin karşılığı olmadığından kayıplar kaçınılmaz olmaktadır. İşte bu tür bir problem ile başa çıkabilmek için encode methodu bize opsiyonel ikinci bir parametre olarak bu durumda ne yapmak istediğimizi seçmemize olanak tanımaktadır. Parametrenin varsayılan değeri olan 'strict' böyle bir durumda programın hata aşağıdakine benzer bir hata mesajı vermesine sebep olacaktır. Değer olarak 'ignore' kullanıldığında hedef karakter kod tablosunda karşılığı olmayan Unicode karakterler yok sayılılar. Diğer bir alternatif ise 'replace' değerinin kullanılmasıdır. Bu durumda hedef karakter kod tablosunda karşılığı olmayan Unicode karakterler '?' (soru işareti) ile değiştirlirler. Konuyla ilgili daha detaylı bilgiye buradan ulaşabilirsiniz.

Bu metodun farklı kullanımlarını aşağıda görebilirsiniz.

Burada yazılanları okuduktan sonra kafanız daha da karıştıysa lütfen yorum kısımını kullanarak geri bildirimde bulunun. Elimden geldiği kadar yardımcı olmaya gayeret ederim. Umarım artık Türkçe karakter problemleri artık eskisi kadar korkutucu değildir...

20 Temmuz 2011 Çarşamba

Bitwise işlemleri ve Bitmask


package main

import (
"fmt"
)

func main() {

var s1 uint32 = 1
var s2 uint32 = 1 << 1
var s4 uint32 = 1 << 3
var s8 uint32 = 1 << 7
var s12 uint32 = 1 << 11
var s16 uint32 = 1 << 16
var s32 uint32 = 1 << 31

fmt.Printf("s1:%v\n", s1)
fmt.Printf("s2:%v\n", s2)
fmt.Printf("s4:%v\n", s4)
fmt.Printf("s8:%v\n", s8)
fmt.Printf("s12:%v\n", s12)
fmt.Printf("s16:%v\n", s16)
fmt.Printf("s32:%v\n", s32)

var value uint32 = s32 | s16 | s12 | s1
var exist uint32 = s16 | s12
var notexist uint32 = s8 | s2

fmt.Printf("value :%b\n", value)
fmt.Printf("xor value:%b\n", (^value))
fmt.Printf("exist :%b\n", exist)
fmt.Printf("notexist :%b\n", notexist)

if exist == (value & exist) {
// "exist" değişkeninde set edilen bitler "value" değişkeninde de set edilmiştir.
fmt.Println("exist match")
} else {
fmt.Println("exist not match")
}


if notexist == (^value & notexist) {
// "notexist" değişkeninde set edilen bitler "value" değişkenin de set edilmemiştir.
fmt.Println("notexist match")
} else {
fmt.Println("notexist not match")
}
}

8 Temmuz 2011 Cuma

Google Go Programlama dili ile Bitwise operatörleri kullanarak Unicode code pointlerini byte dizisine çevirmek


package main

import (
"fmt"
)

func utf8(c int) {
switch {
case (c < 0x80):
putchar(c)

case (c < 0x800):
putchar(0xC0 | c>>6)
putchar(0x80 | c&0x3F)

case (c < 0x10000):
putchar(0xE0 | c>>12)
putchar(0x80 | c>>6&0x3F)
putchar(0x80 | c&0x3F)

case (c < 0x200000):
putchar(0xF0 | c>>18)
putchar(0x80 | c>>12&0x3F)
putchar(0x80 | c>>6&0x3F)
putchar(0x80 | c&0x3F)
}
}

func putchar(c int) {
fmt.Printf("%b ", c)
}

func main() {
utf8(187098)
}


Kaynak: http://czyborra.com/utf/

25 Ocak 2011 Salı

Google Go programlama dili ile Türkçe küçük harf büyük harf dönüşümü

Google Go programlama dili ile Türkçe küçük harf büyük harf dönüşümüne basit bir örnek.

package main

import ("fmt"
 "strings"
 "unicode" 
 )

func main() {
  lower := "abcçdefgğhıijklmnoöprsştuüvyz"
  upper := "ABCÇDEFGĞHIİJKLMNOÖPRSŞTUÜVYZ"
 fmt.Println(strings.ToLowerSpecial(unicode.TurkishCase, upper))
 fmt.Println(strings.ToUpperSpecial(unicode.TurkishCase, lower))
}

9 Şubat 2010 Salı

UTF-8 metin içerisindeki Türkçe karakterleri ASCII benzerlerine dönüştürme

Windows platformunda Mediawiki'ye yüklenen dosya adında Türkçe karakterler varsa dosya upload dizinine Türkçe karakterler bozulmuş olarak kaydedilmektedir ve yüklenmesine rağmen görüntülememektedir. Bu sorunu geçici olarak (Umarım Mediawiki'nin Windows platformundaki Türkçe karakter problemi bir an önce çözülür.) çözmek için yazdığım Mediawiki extension'ında kullandığım Türkçe karakterleri ASCII benzerlerine çeviren kodu aşağıda bulabilirsiniz. Benzer sonuç Upload formunda JavaScript kullanılarak ta gerçekleştirilebilir ancak bence sunucu taraflı bir çözüm daha şık.

4 Ocak 2010 Pazartesi

JavaScript'te Türkçe karakterleri temizleme

Maalesef halen bir çok platformda ve programda Türkçe karakter sorunu yaşamaya devam ediyoruz. Biliyorum Türkçe'ye has karakterleri metin içerisinden temizlemek en son çare ancak bazen başka seçeneğiniz kalmıyor.

Aşağıdaki fonsiyonu kullanarak metin içerisindeki Türkçe karakterleri ASCII tablosundaki benzerlerine çevirebilirsiniz.

function convertToASCII(metin) {
      metin = metin.replace(/\u00c2/g, 'A'); // Â
      metin = metin.replace(/\u00e2/g, 'a'); // â
      metin = metin.replace(/\u00fb/g, 'u'); // û
      metin = metin.replace(/\u00c7/g, 'C'); // Ç
      metin = metin.replace(/\u00e7/g, 'c'); // ç
      metin = metin.replace(/\u011e/g, 'G'); // Ğ
      metin = metin.replace(/\u011f/g, 'g'); // ğ
      metin = metin.replace(/\u0130/g, 'I'); // İ
      metin = metin.replace(/\u0131/g, 'i'); // ı
      metin = metin.replace(/\u015e/g, 'S'); // Ş
      metin = metin.replace(/\u015f/g, 's'); // ş
      metin = metin.replace(/\u00d6/g, 'O'); // Ö
      metin = metin.replace(/\u00f6/g, 'o'); // ö
      metin = metin.replace(/\u00dc/g, 'U'); // Ü
      metin = metin.replace(/\u00fc/g, 'u'); // ü
     
      return metin;   
    }