Yeni eklenen metni değiştirmeden bir değiştirme haritası uygulamak için yalnızca özgün dizgede arama yapın ve ayrı bir sonuç oluşturun. Bu kılavuzda, kısa bir metni birebir ve öngörülebilir değiştirmelerle güncellemek için Mac’te Safari’de JavaScript kullanılıyor.
Masaüstünde bir düzenleme görevinin blue ifadesini green ile ve green ifadesini gold ile değiştirmeyi gerektirdiğini varsayalım. Özgün blue bir kez green olmalıdır. Özgün green ise gold olmalıdır. Aşağıdaki yaklaşım bu ayrımı, çakışan ifadeleri, alıntılanmış metni ve noktalama işaretleri içeren değiştirme değerlerini ele alır.
Değiştirme döngüsü metni neden iki kez değiştirir?
Her kuralı önceki kuralın çıktısına uygulayan bir döngü, zincirleme değişikliklere yol açar. Bu iki renk kuralında ilk işlem green ekler; ikinci işlem de bu yeni eklenen sözcüğü bulup gold olarak değiştirir. Kuralların sırasını değiştirmek tek bir örneği çözebilir, ancak her harita için güvenilir bir davranış tanımlamaz.
JavaScript’te replace() yeni bir dizge döndürür ve girdiyi değiştirmez. Sonraki çağrılar döndürülen dizgeyi aldığında zincirleme değişiklikler oluşur. Bu nedenle görev için net bir kural gerekir: her eşleşme özgün girdiden gelmelidir. Bkz. MDN’nin String.prototype.replace() belgeleri.
Yazılan girdi The blue cup is beside the green cup. olduğunda amaçlanan sonuç The green cup is beside the gold cup. şeklindedir. Özgün renk sözcüklerinin ikisi de değişirken eklenen green olduğu gibi kalır. Aşağıdaki uygulama bu dönüşümü gerçekleştirir.
Değiştirme anahtarları çakıştığında ne olmalı?
Zincirleme değişiklikleri önlemek başka bir kararı da beraberinde getirir: aynı kaynak metinle birden fazla anahtar eşleştiğinde hangisi kazanır? Bu düzenleme görevi için aşağıdaki açık politikayı kullanın. Bunlar örnek için alınmış tasarım kararlarıdır; JavaScript’in kendiliğinden uyguladığı kurallar değildir.
- Özgün girdiyi soldan sağa tarayın.
- Her konumda eşleşen en uzun anahtarı seçin.
- Devam etmeden önce eşleşen kaynak metnin tamamını tüketin.
- Eşlenen değeri, onu aramadan, olduğu gibi ekleyin.
- Eşleşmeyen metni aynen kopyalayın.
- Harf büyüklüğünü ve boşlukları tam olarak eşleştirin.
Örneğin, hem blue hem de blue vase anahtarsa, aynı başlangıç konumunda blue vase ifadesi kazanır. Daha erken başlayan bir eşleşme, daha sonra başlayan daha uzun bir eşleşmeye öncelik verir. Bir kaynak metin aralığı tüketildikten sonra çakışan bir eşleşme bu aralığı yeniden kullanamaz.
Bu politika, küçük bir birebir ifade düzenleme kümesi için uygundur: belirli ifadeler, kendilerinin daha kısa öneklerine öncelik kazanır. Ayrıca çıktı, farklı uzunluktaki anahtarların girilme sırasından bağımsız olur. Göreviniz farklı bir çakışma politikası gerektiriyorsa haritayı uygulamadan önce buna karar verin.
Dönüşümü Mac’te nasıl çalıştırabilirsiniz?
Safari’de JavaScript konsolu bulunur; dolayısıyla bu örnek için ek bir uygulama yüklemeniz gerekmez. Safari → Settings… → Advanced → Show features for web developers yoluyla Web Inspector’ı etkinleştirin. Ardından Develop → Show Web Inspector seçeneğini açın veya Option-Command-I tuşlarına basın. Bu adımlar WebKit’in Web Inspector kurulum belgelerine dayanır.
Safari’nin adres alanına about:blank yazarak boş bir sayfa açın, ardından denetçisini açıp Console sekmesini seçin. Aşağıdaki programın tamamını konsol istemine yapıştırıp çalıştırın. WebKit, istemin JavaScript’i çalıştırıp sonuçları konsolda görüntülediğini belgeliyor. Bkz. Web Inspector Arayüzü.
Örnek, içe aktarma gerektirmeyen; sentetik metin içeren eksiksiz bir tarayıcı programıdır. Dış işlev, bildirimleri yerel kapsamda tutar; böylece örneği düzenledikten sonra bloğu yeniden yapıştırıp çalıştırabilirsiniz.
(() => {
function applyReplacementMap(input, replacements) {
if (typeof input !== "string" || !(replacements instanceof Map)) {
throw new TypeError("Use a string and a Map.");
}
const entries = [...replacements];
for (const [key, value] of entries) {
if (
typeof key !== "string" ||
key.length === 0 ||
typeof value !== "string"
) {
throw new TypeError(
"Keys must be nonempty strings; values must be strings."
);
}
}
entries.sort((a, b) => b[0].length - a[0].length);
const output = [];
let position = 0;
while (position < input.length) {
const entry = entries.find(([key]) =>
input.startsWith(key, position)
);
if (entry) {
output.push(entry[1]);
position += entry[0].length;
} else {
output.push(input.slice(position, position + 1));
position += 1;
}
}
return output.join("");
}
const replacements = new Map([
["blue", "green"],
["green", "gold"],
["blue vase", "green bowl"],
["[tag]", "$&"],
["(draft)", ""]
]);
const input =
'The blue vase is beside the green cup. Label: "[tag]". Note: (draft)';
const result = applyReplacementMap(input, replacements);
console.log(result);
})();
Günlüğe yazılan metin aşağıda gösterilmiştir. Note: ifadesinden sonra sondaki bir boşluk kalır; çünkü kural yalnızca (draft) ifadesini silerek önceki boşluğu korur.
The green bowl is beside the gold cup. Label: \"$&\". Note:
Örneği uyarlamak için input ifadesini ve new Map(...) içindeki çiftleri düzenleyin. Her çift, önce tam kaynak dizgeyi, ardından da onun yerine kullanılacak değeri içerir. Her anahtar için tek bir kayıt kullanın: JavaScript haritalarında anahtarlar benzersizdir; dolayısıyla aynı anahtarı tekrarlamak başka bir rakip kural oluşturmak yerine değerini günceller. Bkz. MDN’nin Map başvurusu.
Eklenen metin neden olduğu gibi kalır?
position adlı imleç yalnızca input boyunca ilerler. Eşleştirme, MDN’nin startsWith() başvurusunda belgelendiği üzere, belirtilen konumda bir dizgeyi kontrol eden ve büyük-küçük harfe duyarlı olan input.startsWith(key, position) ile yapılır. Çıktı dizisi hiçbir zaman arama hedefi olarak kullanılmaz.
Hiçbir kural eşleşmediğinde slice(position, position + 1) bir sonraki kaynak birimini kopyalar. İkinci dilim indeksi hariç tutulur ve dilimleme özgün dizgeyi değiştirmez. Belgelenen bu anlamlar, işlevin sonucu ayrı olarak oluştururken eşleşmeyen içeriği korumasını sağlar. Bkz. MDN’nin slice() başvurusu.
Örnek, blue vase yerine green bowl ekler. Eklenen bu ifade başka bir anahtar olan green içerse de işlev özgün ifadenin ötesine geçmiş olur. green bowl ifadesini hiçbir zaman incelemez. Girdinin ilerleyen kısmındaki özgün green ise yine gold olarak değişir.
Değerler ve silme işlemi nasıl birebir uygulanır?
Bu uygulama, değiştirme değerlerini doğrudan ekler. Dolayısıyla $&, $& olarak kalır; anahtarlardaki köşeli parantezler veya normal parantezler sıradan karakterlerdir. Boş bir değiştirme dizgesi, eşleşen kaynak metin aralığını siler. Bu işlevde herhangi bir düzenli ifade ya da değiştirme şablonu yorumu yapılmaz.
Boş anahtarlar kaynak metin tüketmedikleri için reddedilir; bunlara izin vermek eşleşen dalın ilerlemesini engeller. Dönüşüm başlamadan önce dizge olmayan değerler de reddedilir. Böylece hatalı bir değer sessizce dönüştürülmek ya da atlanmak yerine fark edilir. Boş bir harita girdiyi değiştirmeden döndürür; boş bir girdi ise boş dizge döndürür.
Kopyalamadan önce sonucu nasıl kontrol etmelisiniz?
İstediğiniz davranışı ayırt eden küçük örnekler kullanın. Aşağıdaki beklenen sonuçlar yukarıdaki uygulamaya göre belirlenmiştir. Aksi belirtilmedikçe örnek haritayı kullanırlar.
| Kontrol | Girdi | Beklenen sonuç |
|---|---|---|
| Eklenen metin değişmeden kalır | blue green |
green gold |
| Daha uzun ifade kazanır | blue vase |
green bowl |
| Değiştirme noktalaması birebir kalır | [tag] |
$& |
| Eşleştirme harf büyüklüğüne duyarlıdır | Blue |
Blue |
| Eşleştirme alt dizgeleri de kapsar | blueprint |
greenprint |
| Tırnaklar korunur | \"blue\" |
\"green\" |
| Boş harita | blue |
blue |
Değiştirme işlevi ve bu davranışlar, silme, boş girdi, çakışan eşleşmeler ve geçersiz haritanın reddedilmesiyle birlikte Node.js 20.19.2’de kontrol edildi. Bu, JavaScript dönüşümünün bu çalışma ortamındaki davranışını doğrular; Safari arayüzü adımları ise WebKit’in belgelerine dayanır.
Alt dize kontrolüne özellikle dikkat edin. Bu işlev sözcük sınırlarını kendiliğinden çıkarmaz veya dil bilgisini düzeltmez. Bir anahtar daha uzun bir sözcüğün içinde eşleşebilir ve metni kaldırmak fazladan boşluklar bırakabilir. İfade düzenleme görevinde çevredeki karakterleri inceleyin ve haritayı amaçlanan değişikliğin gerektirdiği ölçüde özgül tutun.
Ayrıca tek seferlik uygulama ile yinelenen uygulamayı birbirinden ayırın. İşlevi özgün metin üzerinde bir kez çalıştırmak, o çalıştırma içindeki zincirleme değişiklikleri önler. Sonucu yeniden işleve girdi olarak vermek yeni bir dönüşüm başlatır; böylece daha önce eklenmiş sözcükler eşleşebilir. Bir düzenlemeyi yeniden çalıştırırken kaynak olarak özgün input metnini kullanın.
Tek bir JavaScript replace çağrısı da aynı şeyi yapabilir mi?
Evet. Küçük ve sabit bir basit anahtar kümesi için tek bir genel düzenli ifade ve değiştirme geri çağrısı aynı zincirleme olmayan davranışı sağlayabilir. ECMAScript belirtimi, kaynak dizgedeki eşleşmeleri toplar ve ardından değiştirme sonucunu oluşturur. Bkz. belirtilen düzenli ifade değiştirme algoritması.
Ayrı ve eksiksiz olan bu örnek, sabit iki renk anahtarını ele alır:
(() => {
const replacements = new Map([
["blue", "green"],
["green", "gold"]
]);
const input = "The blue cup is beside the green cup.";
const result = input.replace(
/blue|green/g,
(word) => replacements.get(word)
);
console.log(result);
// The green cup is beside the gold cup.
})();
g bayrağı, ifadenin tüm eşleşmeleri değiştirmesini sağlar. Geri çağrıdan değer döndürmek, aksi hâlde eşleşen alt dizge anlamına gelen $& gibi değiştirme dizgesi kalıplarını kullanma gereğini de ortadan kaldırır. MDN bu iki davranışı da replace() başvurusunda açıkça belgeliyor.
Noktalama işaretleri içeren ve değişen bir harita için düzenli ifade oluşturmak ek gereklilikler getirir. Anahtarların birebir kalıp olarak ele alınması için kaçırılması ve çakışan alternatifler için açık bir öncelik belirlenmesi gerekir. JavaScript, kalıpları birebir oluşturmak için RegExp.escape() sağlar; ancak MDN, eski tarayıcıların bunu desteklemeyebileceğini belirtiyor. Bkz. MDN’nin RegExp.escape() belgeleri.
Buradaki masaüstü ifade düzenleme görevi için birebir tarayıcı bu kararları görünür kılar: özgün metni okur, her konumda en uzun anahtarı seçer ve her değiştirme değerini olduğu gibi ekler. İşlevi özgün metin üzerinde çalıştırın, ortaya çıkan ifadeyi ve boşlukları inceleyin, ardından tamamlanan metni belgenize kopyalayın.
Kaynaklar
- Web Inspector’ı Etkinleştirme — WebKit.
- Web Inspector Arayüzü — WebKit.
- String.prototype.replace() — MDN Web Docs, Mozilla.
- Map — MDN Web Docs, Mozilla.
- String.prototype.startsWith() — MDN Web Docs, Mozilla.
- String.prototype.slice() — MDN Web Docs, Mozilla.
- ECMAScript Dil Belirtimi: RegExp.prototype [ %Symbol.replace% ] — Ecma International, TC39.
- RegExp.escape() — MDN Web Docs, Mozilla.