كيف أقسم النص إلى جمل؟

استخدم Intl.Segmenter لتقسيم النص إلى جمل مع اكتشاف حدود يراعي اللغة المحلية ويتعامل مع علامات الترقيم والاختصارات والقواعد الخاصة باللغة.

مقدمة

عندما تقوم بمعالجة النص للترجمة أو التحليل أو العرض، غالبًا ما تحتاج إلى تقسيمه إلى جمل فردية. النهج البسيط باستخدام التعبيرات النمطية يفشل لأن حدود الجملة أكثر تعقيدًا من النقاط المتبوعة بمسافات. يمكن أن تنتهي الجمل بعلامات استفهام أو علامات تعجب أو علامات الحذف. تظهر النقاط في الاختصارات مثل "د." أو "شركة" دون إنهاء الجمل. تستخدم اللغات المختلفة علامات ترقيم مختلفة كفواصل للجمل.

تحل واجهة برمجة التطبيقات Intl.Segmenter هذه المشكلة من خلال توفير اكتشاف حدود الجملة المتوافق مع اللغة المحلية. فهي تفهم قواعد تحديد حدود الجملة في اللغات المختلفة وتتعامل مع الحالات الخاصة مثل الاختصارات والأرقام وعلامات الترقيم المعقدة تلقائيًا.

المشكلة في التقسيم على النقاط

يمكنك محاولة تقسيم النص إلى جمل عن طريق التقسيم على النقاط المتبوعة بمسافات.

const text = "Hello world. How are you? I am fine.";
const sentences = text.split(". ");
console.log(sentences);
// ["Hello world", "How are you? I am fine."]

هذا النهج يواجه مشاكل متعددة. أولاً، لا يتعامل مع علامات الاستفهام أو علامات التعجب. ثانيًا، يتعطل عند الاختصارات التي تحتوي على نقاط. ثالثًا، يزيل النقطة من كل جملة باستثناء الجملة الأخيرة. رابعًا، لا يعمل عندما تكون هناك مسافات متعددة بعد النقاط.

const text = "Dr. Smith works at Acme Inc. He starts at 9 a.m.";
const sentences = text.split(". ");
console.log(sentences);
// ["Dr", "Smith works at Acme Inc", "He starts at 9 a.m."]

ينقسم النص بشكل غير صحيح عند "Dr." و "Inc." لأن هذه الاختصارات تحتوي على نقاط. أنت بحاجة إلى نهج أكثر ذكاءً يفهم قواعد حدود الجملة.

استخدام تعبير منتظم أكثر تعقيدًا

يمكنك تحسين التعبير المنتظم للتعامل مع المزيد من الحالات.

const text = "Hello world. How are you? I am fine!";
const sentences = text.split(/[.?!]\s+/);
console.log(sentences);
// ["Hello world", "How are you", "I am fine", ""]

هذا يقسم النص عند النقاط وعلامات الاستفهام وعلامات التعجب المتبوعة بمسافة بيضاء. يتعامل مع المزيد من الحالات ولكنه لا يزال يفشل مع الاختصارات ويُنشئ سلاسل نصية فارغة. كما أنه يزيل علامات الترقيم من كل جملة.

const text = "Dr. Smith works at Acme Inc. He starts at 9 a.m.";
const sentences = text.split(/[.?!]\s+/);
console.log(sentences);
// ["Dr", "Smith works at Acme Inc", "He starts at 9 a", "m", ""]

لا يمكن لنهج التعبير المنتظم التمييز بشكل موثوق بين النقاط التي تنهي الجمل والنقاط التي تظهر في الاختصارات. يصبح بناء تعبير منتظم شامل يتعامل مع جميع الحالات الاستثنائية غير عملي. أنت بحاجة إلى حل يفهم القواعد اللغوية.

استخدام Intl.Segmenter لتقسيم الجمل

يُنشئ مُنشئ Intl.Segmenter مُقسِّمًا يقسم النص بناءً على قواعد خاصة باللغة المحلية. تحدد لغة محلية وتضبط خيار granularity على "sentence".

const segmenter = new Intl.Segmenter("en", { granularity: "sentence" });
const text = "Hello world. How are you? I am fine!";
const segments = segmenter.segment(text);

for (const segment of segments) {
  console.log(segment.segment);
}
// "Hello world. "
// "How are you? "
// "I am fine!"

تُرجع طريقة segment() كائنًا قابلاً للتكرار ينتج كائنات مقطع. يحتوي كل كائن مقطع على خاصية segment تحتوي على نص ذلك المقطع. يحافظ المُقسِّم على علامات الترقيم والمسافات البيضاء في نهاية كل جملة.

يمكنك تحويل المقاطع إلى مصفوفة باستخدام Array.from().

const segmenter = new Intl.Segmenter("en", { granularity: "sentence" });
const text = "Hello world. How are you? I am fine!";
const segments = segmenter.segment(text);
const sentences = Array.from(segments, s => s.segment);
console.log(sentences);
// ["Hello world. ", "How are you? ", "I am fine!"]

هذا يُنشئ مصفوفة حيث كل عنصر هو جملة بعلامات الترقيم والمسافات الأصلية الخاصة بها.

كيفية تعامل Intl.Segmenter مع الاختصارات

يفهم المقسم أنماط الاختصارات الشائعة ولا يقوم بالتقسيم عند النقاط التي تظهر داخل الاختصارات.

const segmenter = new Intl.Segmenter("en", { granularity: "sentence" });
const text = "Dr. Smith works at Acme Inc. He starts at 9 a.m.";
const segments = segmenter.segment(text);
const sentences = Array.from(segments, s => s.segment);
console.log(sentences);
// ["Dr. Smith works at Acme Inc. ", "He starts at 9 a.m."]

ينقسم النص بشكل صحيح إلى جملتين. النقاط في "Dr." و"Inc." و"a.m." لا تؤدي إلى فواصل الجمل لأن المقسم يتعرف عليها كاختصارات. هذا التعامل التلقائي مع الحالات الخاصة هو ما يجعل Intl.Segmenter متفوقًا على أساليب التعبيرات النمطية.

إزالة المسافات البيضاء من الجمل

يتضمن المقسم المسافات البيضاء اللاحقة في كل جملة. يمكنك إزالة هذه المسافات البيضاء إذا لزم الأمر.

const segmenter = new Intl.Segmenter("en", { granularity: "sentence" });
const text = "Hello world. How are you? I am fine!";
const segments = segmenter.segment(text);
const sentences = Array.from(segments, s => s.segment.trim());
console.log(sentences);
// ["Hello world.", "How are you?", "I am fine!"]

تقوم طريقة trim() بإزالة المسافات البيضاء في بداية ونهاية كل جملة. هذا مفيد عندما تحتاج إلى حدود جمل نظيفة بدون مسافات إضافية.

الحصول على البيانات الوصفية للمقاطع

يتضمن كل كائن مقطع بيانات وصفية حول موضع المقطع في النص الأصلي.

const segmenter = new Intl.Segmenter("en", { granularity: "sentence" });
const text = "Hello world. How are you?";
const segments = segmenter.segment(text);

for (const segment of segments) {
  console.log({
    text: segment.segment,
    index: segment.index,
    input: segment.input
  });
}
// { text: "Hello world. ", index: 0, input: "Hello world. How are you?" }
// { text: "How are you?", index: 13, input: "Hello world. How are you?" }

تشير خاصية index إلى المكان الذي يبدأ فيه المقطع في النص الأصلي. تحتوي خاصية input على النص الأصلي الكامل. هذه البيانات الوصفية مفيدة عندما تحتاج إلى تتبع مواضع الجمل أو إعادة بناء النص الأصلي.

تقسيم الجمل في لغات مختلفة

تمتلك اللغات المختلفة قواعد مختلفة لحدود الجمل. يقوم المُقسِّم بتكييف سلوكه بناءً على اللغة المحددة.

في اللغة اليابانية، يمكن أن تنتهي الجمل بنقطة كاملة العرض تسمى كوتن.

const segmenter = new Intl.Segmenter("ja", { granularity: "sentence" });
const text = "私は猫です。名前はまだない。";
const segments = segmenter.segment(text);
const sentences = Array.from(segments, s => s.segment);
console.log(sentences);
// ["私は猫です。", "名前はまだない。"]

ينقسم النص بشكل صحيح عند نهايات الجمل اليابانية. لن يتعرف المُقسِّم المُعد للغة الإنجليزية على هذه الحدود بشكل صحيح.

في اللغة الهندية، يمكن أن تنتهي الجمل بشريط عمودي يسمى بورنا فيرام.

const segmenter = new Intl.Segmenter("hi", { granularity: "sentence" });
const text = "यह एक वाक्य है। यह दूसरा वाक्य है।";
const segments = segmenter.segment(text);
const sentences = Array.from(segments, s => s.segment);
console.log(sentences);
// ["यह एक वाक्य है। ", "यह दूसरा वाक्य है।"]

يتعرف المُقسِّم على النقطة الكاملة في الديفاناغاري كحد للجملة. هذا السلوك المراعي للغة أمر بالغ الأهمية لمعالجة النصوص المتعددة اللغات.

استخدام اللغة الصحيحة للنصوص متعددة اللغات

عند معالجة نص يحتوي على لغات متعددة، اختر اللغة التي تتطابق مع اللغة الأساسية للنص. يستخدم المُقسِّم اللغة المحددة لتحديد قواعد الحدود التي سيطبقها.

const englishText = "Hello world. How are you?";
const japaneseText = "私は猫です。名前はまだない。";

const englishSegmenter = new Intl.Segmenter("en", { granularity: "sentence" });
const japaneseSegmenter = new Intl.Segmenter("ja", { granularity: "sentence" });

const englishSentences = Array.from(
  englishSegmenter.segment(englishText),
  s => s.segment
);

const japaneseSentences = Array.from(
  japaneseSegmenter.segment(japaneseText),
  s => s.segment
);

console.log(englishSentences);
// ["Hello world. ", "How are you?"]

console.log(japaneseSentences);
// ["私は猫です。", "名前はまだない。"]

إنشاء مُقسِّمات منفصلة لكل لغة يضمن اكتشاف الحدود بشكل صحيح. إذا كنت تعالج نصًا حيث اللغة غير معروفة، يمكنك استخدام لغة عامة مثل "en" كحل بديل، على الرغم من أن هذا يقلل من الدقة للنصوص غير الإنجليزية.

التعامل مع النص بدون حدود للجمل

عندما لا يحتوي النص على علامات نهاية الجملة، يقوم المقسّم بإرجاع النص بأكمله كمقطع واحد.

const segmenter = new Intl.Segmenter("en", { granularity: "sentence" });
const text = "Hello world";
const segments = segmenter.segment(text);
const sentences = Array.from(segments, s => s.segment);
console.log(sentences);
// ["Hello world"]

هذا السلوك صحيح لأن النص لا يحتوي على أي حدود للجمل. المقسّم لا يقوم بتقسيم النص الذي يشكل جملة واحدة بشكل مصطنع.

التعامل مع السلاسل النصية الفارغة

يتعامل المقسّم مع السلاسل النصية الفارغة عن طريق إرجاع مكرر فارغ.

const segmenter = new Intl.Segmenter("en", { granularity: "sentence" });
const text = "";
const segments = segmenter.segment(text);
const sentences = Array.from(segments, s => s.segment);
console.log(sentences);
// []

هذا ينتج مصفوفة فارغة، وهي النتيجة المتوقعة للمدخلات الفارغة.

إعادة استخدام المقسّمات لتحسين الأداء

إنشاء مقسّم له بعض التكلفة الحسابية. عندما تحتاج إلى تقسيم نصوص متعددة بنفس اللغة والخيارات، قم بإنشاء المقسّم مرة واحدة وأعد استخدامه.

const segmenter = new Intl.Segmenter("en", { granularity: "sentence" });

const texts = [
  "First text. With two sentences.",
  "Second text. With three sentences. And more.",
  "Third text."
];

texts.forEach(text => {
  const sentences = Array.from(segmenter.segment(text), s => s.segment);
  console.log(sentences);
});
// ["First text. ", "With two sentences."]
// ["Second text. ", "With three sentences. ", "And more."]
// ["Third text."]

إعادة استخدام المقسّم أكثر كفاءة من إنشاء مقسّم جديد لكل نص.

بناء دالة لعد الجمل

يمكنك استخدام المقسّم لعد الجمل في النص.

function countSentences(text, locale = "en") {
  const segmenter = new Intl.Segmenter(locale, { granularity: "sentence" });
  const segments = segmenter.segment(text);
  return Array.from(segments).length;
}

console.log(countSentences("Hello world. How are you?"));
// 2

console.log(countSentences("Dr. Smith works at Acme Inc. He starts at 9 a.m."));
// 2

console.log(countSentences("Single sentence"));
// 1

console.log(countSentences("私は猫です。名前はまだない。", "ja"));
// 2

تقوم هذه الدالة بإنشاء مقسّم، وتقسيم النص، وإرجاع عدد المقاطع. وهي تتعامل بشكل صحيح مع الاختصارات وحدود الجمل الخاصة باللغة.

بناء دالة لاستخراج الجمل

يمكنك إنشاء دالة تستخرج جملة محددة من النص حسب الفهرس.

function getSentence(text, index, locale = "en") {
  const segmenter = new Intl.Segmenter(locale, { granularity: "sentence" });
  const segments = Array.from(segmenter.segment(text), s => s.segment);
  return segments[index] || null;
}

const text = "First sentence. Second sentence. Third sentence.";

console.log(getSentence(text, 0));
// "First sentence. "

console.log(getSentence(text, 1));
// "Second sentence. "

console.log(getSentence(text, 2));
// "Third sentence."

console.log(getSentence(text, 3));
// null

تقوم هذه الدالة بإرجاع الجملة في الفهرس المحدد، أو null إذا كان الفهرس خارج النطاق.

التحقق من دعم المتصفح وبيئة التشغيل

واجهة برمجة التطبيقات Intl.Segmenter متاحة في المتصفحات الحديثة و Node.js. أصبحت جزءًا من الأساس القياسي لمنصة الويب في أبريل 2024 وهي مدعومة في جميع محركات المتصفحات الرئيسية.

يمكنك التحقق مما إذا كانت واجهة البرمجة متاحة قبل استخدامها.

if (typeof Intl.Segmenter !== "undefined") {
  const segmenter = new Intl.Segmenter("en", { granularity: "sentence" });
  const text = "Hello world. How are you?";
  const sentences = Array.from(segmenter.segment(text), s => s.segment);
  console.log(sentences);
} else {
  console.log("Intl.Segmenter is not supported");
}

بالنسبة للبيئات التي لا تدعم هذه الواجهة، تحتاج إلى توفير بديل. يستخدم البديل البسيط تقسيمًا أساسيًا باستخدام التعبيرات النمطية (regex)، على الرغم من أن هذا يفقد دقة التقسيم المتوافق مع اللغة المحلية.

function splitSentences(text, locale = "en") {
  if (typeof Intl.Segmenter !== "undefined") {
    const segmenter = new Intl.Segmenter(locale, { granularity: "sentence" });
    return Array.from(segmenter.segment(text), s => s.segment);
  }

  // Fallback for older environments
  return text.split(/[.!?]\s+/).filter(s => s.length > 0);
}

console.log(splitSentences("Hello world. How are you?"));
// ["Hello world. ", "How are you?"]

تستخدم هذه الدالة Intl.Segmenter عندما تكون متاحة وترجع إلى التقسيم باستخدام التعبيرات النمطية في البيئات القديمة. يفقد البديل ميزات مثل التعامل مع الاختصارات واكتشاف حدود اللغة المحددة، ولكنه يوفر وظائف أساسية.