Başka bir yerden gelmiş metni temizlemek
Bir PDF'ten, bir hesap tablosu hücresinden, bir CMS alanından, bir e-posta istemcisinden ya da bir sohbet uygulamasından gelen metin, kendisini üreten şeyin biçimlendirme kararlarını da beraberinde taşır ve bu kararların neredeyse hiçbiri ekranda görünmez. Sonuç, doğru görünen, doğru yazdırılan, sonra da görünürde hiçbir neden yokken bir karşılaştırmada, bir aramada, bir JSON ayrıştırmasında ya da bir veritabanı sorgusunda başarısız olan bir dizedir.
Göremediğiniz karakterler
Bunlar Unicode açısından gerçek karakterlerdir. Yalnızca görünür bir biçimleri yoktur ya da başka bir şeyle aynı biçimdedirler.
| Karakter | Kod noktası | Genelde nereden gelir | Neyi bozar |
|---|---|---|---|
| Bölünmez boşluk | U+00A0 | HTML'de , Word, PDF yerleşimi | boşluktan bölme, tam eşleşmeler |
| Dar bölünmez boşluk | U+202F | Fransız tipografisi, Word'den gelen tarihler | aynısı, daha az belli ederek |
| İdeografik boşluk | U+3000 | CJK giriş yöntemleri | geniş bir aralık gibi görünür |
| Sıfır genişlikli boşluk | U+200B | CMS satır sonu ipuçları, kopyalanmış web metni | görünmez ve boşluk karakteri sayılmaz |
| Sıfır genişlikli birleştirmeyen / birleştiren | U+200C, U+200D | Farsça, Hint yazıları, emoji dizileri | sözcük sınırları, karakter sayıları |
| Sözcük birleştirici | U+2060 | dizgi araçları | görünürde hiçbir şeyi, metinsel her şeyi |
| Yumuşak tire | U+00AD | Word hecelemesi, PDF dışa aktarımları | bir sözcük kendisiyle eşleşmez olur |
| Bayt sırası işareti | U+FEFF | bir UTF-8 dosyasının ilk baytları | ilk satırın ilk alanı |
| Soldan sağa / sağdan sola işareti | U+200E, U+200F | iki yönlü içerik | sayıların çevresinde başıboş işaretler |
| Satır ve paragraf ayırıcı | U+2028, U+2029 | bazı Mac ve yerleşim uygulamaları | satır bölme, eski JavaScript ayrıştırıcıları |
Bir aramanın başarısız olmasının nedeni, aramanın biçimleri değil kod noktalarını karşılaştırmasıdır. Bir PDF size New U+00A0 York verdiyse ve siz sıradan U+0020 boşluğuyla New York yazarsanız iki dize farklıdır ve hiçbir şey size nedenini söylemez.
"New York".includes("New York") false, the gap is U+00A0
" text".trim().length 5, the zero-width space survives
Bölünmez boşluk, çoğu trim işlevi ve çoğu regex motorundaki \s için boşluk sayılır, dolayısıyla dizenin uçlarında kaybolur ve tam da bölmenin düz bir boşluk beklediği ortada hayatta kalır. Sıfır genişlikli boşluk ise hiçbir yerde boşluk sayılmaz, bu yüzden kırpma ve daraltma ona dokunmaz. Bir posta istemcisinden kopyalanmış metinde Email Extractor'ın neden hiçbir şey döndürmediğini de bu açıklar: adresin içindeki tek bir sıfır genişlikli karakter kalıbın eşleşmesini durdurur.
Hidden Character Detector orada gerçekte ne olduğunu gösterir, Unicode Escape ise tek bir değerin kod noktalarını tam olarak verir. Yine de gördüğünüz her şeyi silmeyin: bir emoji dizisi içindeki sıfır genişlikli birleştirici ile Farsça veya Devanagari metindeki sıfır genişlikli birleştirmeyen karakter gürültü değil içeriktir.
Bir kelime işlemcinin sizin yerinize değiştirdiği noktalama
Otomatik düzeltme siz yazarken tipografik karakterleri yerine koyar ve bu değişiklik metinle birlikte belgeden dışarı çıkar.
| Siz şunu yazdınız | Artık elinizde bu var | Kod noktası |
|---|---|---|
' | sağ tek tırnak işareti | U+2019 |
" ve " | sol ve sağ çift tırnak işaretleri | U+201C, U+201D |
sözcükler arasında - | en dash ya da em dash | U+2013, U+2014 |
... | yatay üç nokta | U+2026 |
bir sayıdan önce - | eksi işareti ya da bölünmez tire | U+2212, U+2011 |
Bu, düzyazıda sorunsuz, bir makinenin ayrıştırdığı her şeyde ise ölümcüldür.
{ “name”: “Ada” } unexpected token, only U+0022 is a JSON string quote
git commit -m “fix” the shell sees three words, not one quoted argument
WHERE name = ‘Ada’ SQL syntax error near ‘
10\u201320 not a number range, U+2013 is not a hyphen
CSV'de arıza daha sessizdir. Bir ayrıştırıcı alan sınırlayıcısı olarak yalnızca düz çift tırnağı tanır, dolayısıyla bir kelime işlemcinin kıvrık tırnaklara aldığı bir değer tırnaksız sayılır; içindeki herhangi bir virgül de satırı böler ve ondan sonraki bütün sütunlar kayar. Negatif değerlerinde U+2212 kullanan bir sütun metin olarak içeri alınır ve toplamlar onu sessizce dışarıda bırakır.
Kısa bir değiştirme listesiyle Find and Replace çözümdür, ama bunu yalnızca koda, CSV'ye ya da bir anahtara gidecek metne uygulayın. Yayımlayacağınız düzyazının üzerinde çalıştırmak, bilerek konmuş noktalamayı düzleştirir.
Satır sonları ve satır sonundaki boşluklar
Windows araçları bir satırı CR LF (U+000D U+000A) ile bitirir, Unix araçları yalnızca LF ile bitirir, birkaç çok eski Mac dışa aktarımı da hâlâ yalnızca CR kullanır. Çoğu ayrıştırıcı bununla baş eder. Naif bölme baş edemez.
"UK\r\n" split on "\n" -> "UK\r"
"UK\r" === "UK" false
"UK\r".length 3
Bir tabloda, bir günlükte ya da bir fark görünümünde birebir aynı görünen iki dize yine de bir satır başı, sondaki bir boşluk ya da bir sekme kadar farklı olabilir. Text Diff bir satırı değişmiş olarak işaretlediğinde ve gözle bir değişiklik görünmediğinde cevap budur. Yapıştırılmış bir sütun Quote Lines ya da Join Lines'tan geçtiğinde tırnakların içine başıboş bir boşluk sokan da aynı şeydir.
Tek geçişte normalleştirin; CR LF ile tek başına duran CR'yi birlikte eşleyin (\r\n? yerine \n koyun), yoksa iki adımlı bir değiştirme boş satırları ikiye katlar.
Tek bir harf, onu yazmanın iki yolu
Unicode, aksanlı bir harfin tek bir kod noktası olmasına ya da bir taban harf ile ardından gelen bir birleşen işaret olmasına izin verir. İkisi de doğrudur ve birbirine eşit değildir.
| Biçim | é şöyle saklanır | JavaScript'te kod birimi |
|---|---|---|
| NFC (birleşik) | U+00E9 | 1 |
| NFD (ayrışık) | U+0065 U+0301 | 2 |
macOS geçmişte ayrışık dosya adları vermiştir, birkaç PDF üreticisi ile giriş yöntemi de ayrışık metin üretir, dolayısıyla bir kaynaktan gelen bir değer, klavyeden yazılan aynı değerle eşleşmez.
"é" === "é" false
"é".normalize("NFC") === "é" true
Dolaylı etkiler eşitlikle sınırlı kalmaz. Kod noktalarını karşılaştıran bir sıralama, ayrışık biçimleri düz e harfinin yanına, birleşik biçimleri ise uzağa koyar, dolayısıyla bir ad listesi iki öbek halinde geri gelir. Karakter sayıları biçimden biçime değişir; bu da 280 karakterlik bir sınır ya da bir VARCHAR(50) sütunu için önemlidir ve Text Statistics aynı görünen metin için farklı sayılar bildirir. Kırpma daha kötüsüdür: ayrışık bir dizeyi sabit bir uzunlukta kesmek, taban harf ile aksanının arasına girebilir ve o aksan kendisinden sonra gelen neyse ona iliştirilir; bu yüzden normalleştirilmemiş girdide Truncate Text gözle görülür biçimde yanlış bir son karakter üretebilir.
Uyumluluk biçimleri NFKC ve NFKD daha da ileri gider ve yalnızca başkalarına benzeyen karakterleri de aynılaştırır: U+FB01 bitişik harfi fi olur, tam genişlikli A U+FF21 A olur, mikro işareti U+00B5 Yunan mu harfi U+03BC olur, üst simge ² ise düz bir 2 olur. Bu, bir arama ya da yinelenen kayıt anahtarı için mükemmel, gösterdiğiniz her şey içinse yıkıcıdır, çünkü x² sessizce x2 oluverir.
İşleyen kural şudur: saklama ve gösterim için NFC, yalnızca kimsenin görmediği anahtarlar için NFKC.
Harf durumu dönüşümü tek bir işlem değildir
Büyük harfe çevirmek karakter başına bir eşleme değildir ve yerel ayardan bağımsız da değildir.
- Almanca
ßU+00DF büyük harfteSSolur, dolayısıyla dize uzar ve tekrar küçük harfe dönmek özgün değeri geri vermez. - Yunanca sigma sözcük sonunda
ς, başka yerlerdeσolarak küçülür; bu da gidiş dönüşü yine bozar. - Türkçe ve Azericede noktasız bir
ıU+0131 ile noktalı bir büyükİU+0130 vardır. Bu yerel ayarlardaIküçük harfteı,ibüyük harfteİolur.
Sonuncusu klasik üretim hatasıdır. Bir başlık adını, bir dosya uzantısını ya da bir protokol dizesini küçük harfe çeviren kod, varsayılan yerel ayarı Türkçe olan bir makinede çalışana kadar her yerde çalışır; orada "FILE" küçük harfte fıle olur ve file ile eşleşmeyi bırakır. Java ve .NET'te argümansız yöntemler makinenin varsayılan yerel ayarını kullanır, dolayısıyla tuzak toUpperCase() ve ToUpper() çağrılarıdır; makinenin okuyacağı her şey için değişmez bir yerel ayar belirtin. Karşılaştırma için harf durumu katlama (Python'da casefold()) küçük harfe çevirmeyi geride bırakır, çünkü ß harfini ss olarak ele alır.
Başlık düzeninin tek bir tanımı yoktur; "her sözcüğü büyük harfle başlat" kuralının "The Lord Of The Rings" ve "IPhone" üretmesinin nedeni de budur. Çoğu biçem kılavuzu ilk ve son sözcüğü, ayrıca artikeller, sıralama bağlaçları ve kısa edatlar dışındaki her şeyi büyük harfle başlatır, tireli bir bileşiğin iki yarısını da büyük harfli tutar ve kısaltmalara ya da McDonald, O'Brien veya iPhone gibi içinde büyük harf bulunan adlara hiç dokunmaz.
Programlama harf düzenlerinin kendi sınır sorunu vardır. HTTPResponseCode ifadesini snake case'e çevirmek tümüyle bölücünün ardışık büyük harf dizisini nasıl ele aldığına bağlıdır; Case Converter http_response_code verir, naif bir bölücü h_t_t_p_response_code verir.
İşe yarayan bir sıra
Her adım bir öncekinin çalışmış olduğunu varsayar. Sıra bozulursa birbirleriyle kavga ederler.
- Kodlamayı çözüme kavuşturun.
cafégibi bir mojibake, baytların yanlış kodlamayla çözüldüğü anlamına gelir, dolayısıyla belirtileri yamamak yerine özgün baytları yeniden çözün. BOM'u yalnızca metnin en başında kaldırın. - Satır sonlarını tek bir geçişte LF olarak normalleştirin.
- Gürültü olduğuna karar verdiğiniz biçim karakterlerini kaldırın: yumuşak tireler, sıfır genişlikli boşluk, sözcük birleştirici, bidi işaretleri. Bunu normalleştirmeden önce yapın, çünkü NFC bir taban harf ile aksanını, aralarında duran görünmez bir karakterin ötesinden birleştiremez.
- Benzeşen noktalamayı değiştirin, eğer hedef kod, CSV, JSON ya da bir anahtarsa.
- Unicode normalleştirmesini uygulayın, varsayılan olarak NFC.
- Şimdi boşlukları ele alın. Geriye kalan egzotik boşlukları U+0020'ye çevirin, ardışık olanları daraltın, sonra kırpın. Bunu 3. adımdan önce yapmak, bölünmez bir boşluğun sıradan bir boşlukla buluştuğu her yerde çift boşluk bırakır; 2. adımdan önce kırpmak ise her satırın son değerine yapışık bir satır başı bırakır.
- Harf durumunu en sona bırakın, yerel ayarı açıkça belirterek.
Sözcük düzeyindeki işlemler de 3. adımdan sonra gelir. Yumuşak tireler dururken Split Text ile metin bölmek şişirilmiş sayılar ve yarım sözcükler verir; Censor Text dahil, terim tarayan her şey için aynısı geçerlidir.
Bir değer bütün bunlardan sonra hâlâ eşleşmemekte direniyorsa ona bakmayı bırakın. Uzunluğunu yazdırın, kod noktalarına kaçırın ve kaçırılmış iki biçimi doğrudan karşılaştırın; fark orada, ekranda hiçbir zaman olmadığı kadar açıktır.