مقارنة النصوص مع تجاهل علامات التشكيل
تعلم كيفية مقارنة النصوص مع تجاهل علامات التشكيل باستخدام التطبيع في JavaScript وواجهة Intl.Collator
مقدمة
عند بناء تطبيقات تعمل مع لغات متعددة، غالباً ما تحتاج إلى مقارنة نصوص تحتوي على علامات تشكيل. يجب أن يجد المستخدم الذي يبحث عن "cafe" نتائج لـ "café". يجب أن يتطابق التحقق من اسم المستخدم "Jose" مع "José". تعامل مقارنة النصوص القياسية هذه النصوص على أنها مختلفة، لكن منطق التطبيق الخاص بك يحتاج إلى معاملتها على أنها متساوية.
توفر JavaScript طريقتين لحل هذه المشكلة. يمكنك تطبيع النصوص وإزالة علامات التشكيل، أو استخدام واجهة برمجة التطبيقات المدمجة للترتيب لمقارنة النصوص بقواعد حساسية محددة.
ما هي علامات التشكيل
علامات التشكيل هي رموز توضع فوق الحروف أو تحتها أو من خلالها لتعديل نطقها أو معناها. تسمى هذه العلامات بالحركات الإضافية. تشمل الأمثلة الشائعة العلامة الحادة في "é"، والتلدة في "ñ"، والأوملاوت في "ü".
في Unicode، يمكن تمثيل هذه الأحرف بطريقتين. يمكن لنقطة رمز واحدة أن تمثل الحرف الكامل، أو يمكن لنقاط رمز متعددة أن تجمع حرفاً أساسياً مع علامة تشكيل منفصلة. يمكن تخزين الحرف "é" كـ U+00E9 أو كـ "e" (U+0065) بالإضافة إلى علامة حادة مركبة (U+0301).
متى يجب تجاهل علامات التشكيل في المقارنات
تعد وظيفة البحث حالة الاستخدام الأكثر شيوعاً للمقارنة غير الحساسة لعلامات التشكيل. يتوقع المستخدمون الذين يكتبون استعلامات بدون علامات تشكيل العثور على محتوى يحتوي على أحرف منقطة. يجب أن يجد البحث عن "Muller" النتيجة "Müller".
يتطلب التحقق من صحة إدخال المستخدم هذه الإمكانية عند التحقق مما إذا كانت أسماء المستخدمين أو عناوين البريد الإلكتروني أو المعرفات الأخرى موجودة بالفعل. تريد منع الحسابات المكررة لـ "maria" و "maría".
غالبًا ما تحتاج المقارنات غير الحساسة لحالة الأحرف إلى تجاهل علامات التشكيل في نفس الوقت. عند التحقق من تطابق سلسلتين نصيتين بغض النظر عن الأحرف الكبيرة والصغيرة، عادةً ما تريد تجاهل الاختلافات في علامات التشكيل أيضًا.
إزالة علامات التشكيل باستخدام التطبيع
يحول النهج الأول السلاسل النصية إلى شكل مطبّع حيث يتم فصل الأحرف الأساسية وعلامات التشكيل، ثم يزيل علامات التشكيل.
يحول تطبيع يونيكود السلاسل النصية إلى شكل قياسي. يفصل شكل NFD (التحليل القانوني) الأحرف المركبة إلى أحرفها الأساسية وعلامات الدمج. تصبح السلسلة النصية "café" "cafe" متبوعة بحرف علامة التشكيل الحادة المدمجة.
بعد التطبيع، يمكنك إزالة علامات الدمج باستخدام تعبير نمطي. يحتوي نطاق يونيكود من U+0300 إلى U+036F على علامات التشكيل المدمجة.
function removeAccents(str) {
return str.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
}
const text1 = 'café';
const text2 = 'cafe';
const normalized1 = removeAccents(text1);
const normalized2 = removeAccents(text2);
console.log(normalized1 === normalized2); // true
console.log(normalized1); // "cafe"
تمنحك هذه الطريقة سلاسل نصية بدون علامات تشكيل يمكنك مقارنتها باستخدام عوامل المساواة القياسية.
يمكنك دمج هذا مع تحويل الأحرف الصغيرة للمقارنات غير الحساسة لحالة الأحرف وعلامات التشكيل.
function normalizeForComparison(str) {
return str.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase();
}
const search = 'muller';
const name = 'Müller';
console.log(normalizeForComparison(search) === normalizeForComparison(name)); // true
يعمل هذا النهج بشكل جيد عندما تحتاج إلى تخزين أو فهرسة النسخة المطبّعة من السلاسل النصية للبحث الفعال.
مقارنة السلاسل النصية باستخدام Intl.Collator
يستخدم النهج الثاني واجهة برمجة التطبيقات Intl.Collator، والتي توفر مقارنة السلاسل النصية مع مراعاة اللغة ومستويات حساسية قابلة للتكوين.
يقارن كائن Intl.Collator السلاسل النصية وفقًا لقواعد خاصة باللغة. يتحكم خيار الحساسية في الاختلافات التي تهم عند مقارنة السلاسل النصية.
يتجاهل مستوى الحساسية "base" كلاً من علامات التشكيل واختلافات حالة الأحرف. تعتبر السلاسل النصية التي تختلف فقط في علامات التشكيل أو الأحرف الكبيرة والصغيرة متساوية.
const collator = new Intl.Collator('en', { sensitivity: 'base' });
console.log(collator.compare('café', 'cafe')); // 0 (equal)
console.log(collator.compare('Café', 'cafe')); // 0 (equal)
console.log(collator.compare('café', 'caff')); // -1 (first comes before second)
تُرجع الدالة compare القيمة 0 عندما تكون السلاسل النصية متساوية، ورقمًا سالبًا عندما تأتي السلسلة النصية الأولى قبل الثانية، ورقمًا موجبًا عندما تأتي السلسلة النصية الأولى بعد الثانية.
يمكنك استخدام هذا لفحوصات المساواة أو لترتيب المصفوفات.
const collator = new Intl.Collator('en', { sensitivity: 'base' });
function areEqualIgnoringAccents(str1, str2) {
return collator.compare(str1, str2) === 0;
}
console.log(areEqualIgnoringAccents('José', 'Jose')); // true
console.log(areEqualIgnoringAccents('naïve', 'naive')); // true
للترتيب، يمكنك تمرير طريقة compare مباشرة إلى Array.sort.
const names = ['Müller', 'Martinez', 'Muller', 'Márquez'];
const collator = new Intl.Collator('en', { sensitivity: 'base' });
names.sort(collator.compare);
console.log(names); // Groups variants together
توفر واجهة برمجة التطبيقات Intl.Collator مستويات حساسية أخرى لحالات استخدام مختلفة.
يتجاهل مستوى "accent" حالة الأحرف لكنه يحترم اختلافات العلامات الإعرابية. "Café" يساوي "café" لكن ليس "cafe".
const accentCollator = new Intl.Collator('en', { sensitivity: 'accent' });
console.log(accentCollator.compare('Café', 'café')); // 0 (equal)
console.log(accentCollator.compare('café', 'cafe')); // 1 (not equal)
يتجاهل مستوى "case" العلامات الإعرابية لكنه يحترم اختلافات حالة الأحرف. "café" يساوي "cafe" لكن ليس "Café".
const caseCollator = new Intl.Collator('en', { sensitivity: 'case' });
console.log(caseCollator.compare('café', 'cafe')); // 0 (equal)
console.log(caseCollator.compare('café', 'Café')); // -1 (not equal)
يحترم مستوى "variant" جميع الاختلافات. هذا هو السلوك الافتراضي.
const variantCollator = new Intl.Collator('en', { sensitivity: 'variant' });
console.log(variantCollator.compare('café', 'cafe')); // 1 (not equal)
الاختيار بين التطبيع والترتيب
تنتج كلتا الطريقتين نتائج صحيحة للمقارنة غير الحساسة للعلامات الإعرابية، لكن لهما خصائص مختلفة.
تنشئ طريقة التطبيع سلاسل نصية جديدة بدون علامات إعرابية. استخدم هذا النهج عندما تحتاج إلى تخزين أو فهرسة الإصدارات المطبعة. غالباً ما تخزن محركات البحث وقواعد البيانات النصوص المطبعة للبحث الفعال.
تقارن طريقة Intl.Collator السلاسل النصية دون تعديلها. استخدم هذا النهج عندما تحتاج إلى مقارنة السلاسل النصية مباشرة، مثل التحقق من التكرارات أو ترتيب القوائم. يحترم المرتب قواعد الترتيب الخاصة باللغة التي لا يمكن للمقارنة البسيطة للسلاسل النصية التعامل معها.
تختلف اعتبارات الأداء حسب حالة الاستخدام. إنشاء كائن مرتب مرة واحدة وإعادة استخدامه فعال للمقارنات المتعددة. تطبيع السلاسل النصية فعال عندما تطبع مرة واحدة وتقارن عدة مرات.
تزيل طريقة التطبيع معلومات العلامات الإعرابية بشكل دائم. تحافظ طريقة الترتيب على السلاسل النصية الأصلية بينما تقارنها وفقاً للقواعد التي تحددها.
تصفية المصفوفات باستخدام البحث غير الحساس للعلامات الإعرابية
حالة استخدام شائعة هي تصفية مصفوفة من العناصر بناءً على إدخال المستخدم، مع تجاهل اختلافات العلامات الإعرابية.
const products = [
{ name: 'Café Latte', price: 4.50 },
{ name: 'Crème Brûlée', price: 6.00 },
{ name: 'Croissant', price: 3.00 },
{ name: 'Café Mocha', price: 5.00 }
];
function searchProducts(query) {
const collator = new Intl.Collator('en', { sensitivity: 'base' });
return products.filter(product => {
return collator.compare(product.name.slice(0, query.length), query) === 0;
});
}
console.log(searchProducts('cafe'));
// Returns both Café Latte and Café Mocha
بالنسبة لمطابقة السلاسل النصية الفرعية، يعمل نهج التطبيع بشكل أفضل.
function removeAccents(str) {
return str.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
}
function searchProducts(query) {
const normalizedQuery = removeAccents(query.toLowerCase());
return products.filter(product => {
const normalizedName = removeAccents(product.name.toLowerCase());
return normalizedName.includes(normalizedQuery);
});
}
console.log(searchProducts('creme'));
// Returns Crème Brûlée
يتحقق هذا النهج مما إذا كان اسم المنتج المطبّع يحتوي على استعلام البحث المطبّع كسلسلة نصية فرعية.
التعامل مع مطابقة إدخال النص
عند التحقق من صحة إدخال المستخدم مقابل البيانات الموجودة، تحتاج إلى مقارنة غير حساسة للعلامات التشكيلية لمنع الارتباك والتكرارات.
const existingUsernames = ['José', 'María', 'François'];
function isUsernameTaken(username) {
const collator = new Intl.Collator('en', { sensitivity: 'base' });
return existingUsernames.some(existing =>
collator.compare(existing, username) === 0
);
}
console.log(isUsernameTaken('jose')); // true
console.log(isUsernameTaken('Maria')); // true
console.log(isUsernameTaken('francois')); // true
console.log(isUsernameTaken('pierre')); // false
يمنع هذا المستخدمين من إنشاء حسابات بأسماء تختلف فقط في العلامات التشكيلية أو الأحرف الكبيرة عن الحسابات الموجودة.
دعم المتصفح والبيئة
طريقة String.prototype.normalize مدعومة في جميع المتصفحات الحديثة وبيئات Node.js. لا يدعم Internet Explorer هذه الطريقة.
واجهة Intl.Collator مدعومة في جميع المتصفحات الحديثة وإصدارات Node.js. يتضمن Internet Explorer 11 دعماً جزئياً.
يعمل كلا النهجين بشكل موثوق في بيئات JavaScript الحالية. إذا كنت بحاجة إلى دعم المتصفحات القديمة، فأنت بحاجة إلى polyfills أو تطبيقات بديلة.
قيود إزالة العلامات التشكيلية
تستخدم بعض اللغات العلامات التشكيلية لإنشاء أحرف مميزة، وليس مجرد اختلافات في العلامات. في اللغة التركية، "i" و "ı" حرفان مختلفان. في اللغة الألمانية، "ö" حرف علة مميز، وليس "o" بعلامة تشكيلية.
تؤدي إزالة العلامات التشكيلية إلى تغيير المعنى في هذه الحالات. ضع في اعتبارك ما إذا كانت المقارنة غير الحساسة للعلامات التشكيلية مناسبة لحالة الاستخدام واللغات المستهدفة.
يتعامل نهج المقارنة اللغوية مع هذه الحالات بشكل أفضل لأنه يتبع القواعد الخاصة بكل لغة. يضمن تحديد اللغة الصحيحة في مُنشئ Intl.Collator إجراء مقارنات مناسبة ثقافياً.
const turkishCollator = new Intl.Collator('tr', { sensitivity: 'base' });
const germanCollator = new Intl.Collator('de', { sensitivity: 'base' });
ضع دائماً في اعتبارك اللغات التي يدعمها تطبيقك عند اختيار استراتيجية المقارنة.