如何从具有变音符号但保持剩余字符串的原始变音符号的阿拉伯字符串中查找和删除 first/starting 字符串

How to find and remove first/starting string from an Arabic string having diacritics but maintaining the original diacritics of remaining string

目的是从一个我们不知道它是否有变音符号的阿拉伯字符串中找到并删除一个开头的 string/chars/word ,但必须保留剩余的所有变音符号字符串(如果有).

在 Whosebug 上有很多关于从英文字符串中删除 first/starting string/chars 的答案,但是在 Whosebug 上找不到解决此问题的现有解决方案来保持阿拉伯字符串的平衡它的原始形式。

如果原始字符串在处理之前被规范化(去除变音符号、tanween 等),那么剩余的字符串 returned 将是规范化字符串的平衡部分,而不是原始字符串的剩余部分字符串.

例子。假设以下原始字符串可以是以下任何一种形式(即相同的字符串但不同的变音符号):

1. "السلام عليكم ورحم الله"

2。 "السَلام عليكمُ ورحمُ الله"

3。 "السَلامُ عَليكمُ وروَحمُ الله"

4. "السَّلَامُ عولويْكُمُ ووَروْموُ الله"

现在假设我们想要删除 first/staring 个字符“السلام”,仅当字符串以此类字符开头(确实如此)时,return 剩余的“原始" 字符串及其原始变音符号

当然,我们正在寻找没有变音符号的字符“السلام”,因为我们不知道原始字符串是如何用变音符号格式化的。

因此,在这种情况下,每个字符串的 returned 剩余必须是:

1. “ عليكم ورحм الله”

2。 " عليكمُ ورحموُ الله"

3。 " عَليكمُ وروحموُ الله"

4. " عَلَيْكُمُ وووْموُ الله"

下面的代码适用于英语字符串(还有许多其他解决方案)但不适用于上面解释的阿拉伯语字符串。

function removeStartWord(string,word) {
if (string.startsWith(word)) string=string.slice(word.length);
return string;
}

上述代码利用了从原始字符串中找到的起始字符根据字符长度进行切片的原理;这适用于英文文本。

对于阿拉伯字符串,我们不知道原始字符串的变音符号的形式,因此我们在原始字符串中寻找的 string/characters 的长度将是不同的和未知的。

编辑:添加了示例图像以便更好地说明。

下图table提供了更多示例:

我看不出你的代码有什么问题,但这是另一种方法:

function removeStartWord(string, word) {
  return string.split(' ').filter((_word, index) => index !== 0 || _word.replace(/[^a-zA-Zء-ي]+/g, '') !== word).join(' ');
}

const sampleData = `السَّلَامُ عَلَيْكُمُ وَرَحْمَةُ الله`;

console.log('sampleData ...', sampleData);
console.log(
  "removeStartWord(sampleData, 'السلام') ...",
  removeStartWord(sampleData, 'السلام')
);
console.log(
  "removeStartWord(sampleData, 'الس') ...",
  removeStartWord(sampleData, 'الس')
);
console.log(
  "removeStartWord(sampleData, 'السلام ') ...",
  removeStartWord(sampleData, 'السلام ')
);
console.log(
  "removeStartWord(sampleData, ' السلام') ...",
  removeStartWord(sampleData, ' السلام')
);
.as-console-wrapper { min-height: 100%!important; top: 0; }

\p{Arabic} 一样与 regex unicode escapes might already be good enough for what the OP is looking for, though JavaScript does not support unicode scripts 一起工作。

/^[\p{L}\p{M}]+\p{Z}+/gmu together with replace 这样的基于类别的模式已经完全满足了 OP 的要求...

find and remove first starting word from an arabic string having diacritis

模式……^[\p{L}\p{M}]+\p{Z}+……读起来像这样……

  • ^...从新行的开头开始...
  • [ ... ]+ ... 在列表中查找指定字符的第一个字符 class ...
    • \p{L} ...任何一种L来自任何语言的字母,
    • \p{M} ... 或打算与另一个字符组合的字符(例如重音符号、变音符号、封闭框等)
  • ... 后跟 \p{Z}+ ... 任何类型的空格或不可见分隔符中的至少一个。

console.log(`السلام عليكم ورحمة الله
السَلام عليكمُ ورحمةُ الله
السَلامُ عَليكمُ ورَحمةُ الله
السَّلَامُ عَلَيْكُمُ وَرَحْمَةُ الله`.replace(/^[\p{L}\p{M}]+\p{Z}+/gmu, ''));
.as-console-wrapper { min-height: 100%!important; top: 0; }

编辑

由于现在很清楚 OP 真正想要什么,所以上述方法仍然存在,只是通过利用 replacer function with additional comparison logic based on an Intl.Collator object which takes Arabic and base letter comparison into account.

提升到了一个新的水平

整理器通过提供(除了 'ar' 局部变量之外)一个具有 基本敏感度 的选项来进行最不严格的初始化。因此,在通过 collat​​or 的 compare 方法比较两个相似(但不完全相等)的字符串时,例如'السلام''السَّلَامُ' 将被视为相等,尽管后者具有(很多)变音符号。

证明/例子...

const baseLetterCollator = new Intl.Collator('ar', { sensitivity: 'base' } );

console.log(
  "('السلام عليكم ورحمة الله' === 'السَّلَامُ عَلَيْكُمُ وَرَحْمَةُ الله') ?..",
  ('السلام عليكم ورحمة الله' === 'السَّلَامُ عَلَيْكُمُ وَرَحْمَةُ الله')
);
console.log('\n');

console.log(`new Intl.Collator()
  .compare('السلام عليكم ورحمة الله' ,'السَّلَامُ عَلَيْكُمُ وَرَحْمَةُ الله') === 0

  ?..`,
  new Intl.Collator()
    .compare('السلام عليكم ورحمة الله' ,'السَّلَامُ عَلَيْكُمُ وَرَحْمَةُ الله') === 0
);
console.log(`new Intl.Collator('ar', { sensitivity: 'base' } )
  .compare('السلام عليكم ورحمة الله' ,'السَّلَامُ عَلَيْكُمُ وَرَحْمَةُ الله') === 0

  ?..`,
  new Intl.Collator('ar', { sensitivity: 'base' } )
    .compare('السلام عليكم ورحمة الله' ,'السَّلَامُ عَلَيْكُمُ وَرَحْمَةُ الله') === 0
);
.as-console-wrapper { min-height: 100%!important; top: 0; }

综合以上所说...最终的解决方案...

function removeFirstMatchingWordFromEveryNewLine(search, multilineString) {
  const baseLetterCollator
    // - [ar]abic
    // - base sensitivity
    //   ... only strings that differ in base letters compare as unequal.
    = new Intl.Collator('ar', { sensitivity: 'base' } );

  const replacer = word => {
    return (baseLetterCollator.compare(search, word.trim()) === 0)
      ? ''    // - remove the matching word (whitespace included).
      : word; // - keep the word since there was no match. 
  }
  const regXFirstLineWord = /^[\p{L}\p{M}]+\p{Z}+/gmu;

  search = String(search).trim();

  return String(multilineString).replace(regXFirstLineWord, replacer);  
}
const sampleData = `السلام عليكم ورحمة الله
السَلام عليكمُ ورحمةُ الله
أهلا ومرحبا
السَلامُ عَليكمُ ورَحمةُ الله
السَّلَامُ عَلَيْكُمُ وَرَحْمَةُ الله`;

console.log('sampleData ...', sampleData);
console.log(
  "removeFirstMatchingWordFromEveryNewLine('السلام', sampleData) ...",
  removeFirstMatchingWordFromEveryNewLine('السلام', sampleData)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }

为了跟踪讨论,我添加了一个新答案, 请试试这个!

function removeStartWord(string, word) {
  const alphabeticString =  string.replace(/[^a-zA-Zء-ي0-9/]+/g, '');
  if(!alphabeticString.startsWith(word)) return string;
  const letters = [...word];
  let cleanString = '';
  string.split('').forEach((_letter) => {
    if(letters.indexOf(_letter) > -1) {
      delete letters[letters.indexOf(_letter)]
    }else{
      cleanString += _letter;
    }
  });
  return cleanString.replace(/[^a-zA-Zء-ي0-9/\s]*/i, '');
}

const sampleData = `السَّلَامُ عَلَيْكُمُ وَرَحْمَةُ الله`;

console.log('sampleData ...', sampleData);
console.log(
  "removeStartWord(sampleData, 'السلام') ...",
  removeStartWord(sampleData, 'السلام')
);
console.log(
  "removeStartWord(sampleData, 'الس') ...",
  removeStartWord(sampleData, 'الس')
);
console.log(
  "removeStartWord(sampleData, 'السلام ') ...",
  removeStartWord(sampleData, 'السلام ')
);
console.log(
  "removeStartWord(sampleData, ' السلام') ...",
  removeStartWord(sampleData, ' السلام')
);
.as-console-wrapper { min-height: 100%!important; top: 0; }

由于需求发生变化 (d) 并且信息逐条传入,...

"The [...] answer removes the first matching word assuming a space after the word. But the string of characters we are looking for may not necessarily be followed by a space (i.e. not a standalone word). For example, removing the characters "السيد" from the sentence "السيد/محسن اليافعي" and returning only "/محسن اليافعي". – Mohsen Alyafei"

...我也会从空白开始sheet.

基于 Intl.Collator based locale compare against the matching result of a Unicode property escapes 的正则表达式匹配任何阿拉伯语 单词 的组合方法,无论组合字符如重音符号、变音符号等不能再使用,如果finding/matching 任何类型的字符串(这里是新行的开头)。

但是任何试图天真地迭代字符串以逐个字符地比较两个字符串的方法都将失败。

示例代码胜于雄辩...让我们看看...

console.log(`
  ... remember ...
  new Intl.Collator('ar', { sensitivity: 'base' } )
    .compare('السَّلَامُ' ,'السلام') === 0

  ?..`, new Intl.Collator('ar', { sensitivity: 'base' } )
    .compare('السَّلَامُ' ,'السلام') === 0, `

  ... but ...
  new Intl.Collator('ar')
    .compare('السَّلَامُ' ,'السلام') === 0

  ?..`, new Intl.Collator('ar')
    .compare('السَّلَامُ' ,'السلام') === 0
);
console.log('\n... explanation ...\n\n');

console.log("'السلام'.length ...", 'السلام'.length);
console.log("'السَّلَامُ'.length ...", 'السَّلَامُ'.length);

console.log("'السلام'.split('') ...", 'السلام'.split(''));
console.log("'السَّلَامُ'.split('') ...", 'السَّلَامُ'.split(''));
.as-console-wrapper { min-height: 100%!important; top: 0; }

幸运的是 Intl, ECMAScript's Internationalization API, can help here too. There is Intl.Segmenter which will help breaking down the string(s) into comparable segments. For the OP's use case it will be good enough to do it on the default granularity level of 'grapheme',这似乎等于 分割成 可比较的语言环境 字母 ...

console.log(`[
  ...new Intl.Segmenter('ar', { granularity: 'grapheme' }).segment('السلام')
]
.map(({ segment }) => segment) ...`, [

  ...new Intl.Segmenter('ar', { granularity: 'grapheme' }).segment('السلام')
  ]
  .map(({ segment }) => segment)
);
console.log(`[
  ...new Intl.Segmenter('ar').segment('السَّلَامُ')
]
.map(({ segment }) => segment) ...`, [

    ...new Intl.Segmenter('ar').segment('السَّلَامُ')
  ]
  .map(({ segment }) => segment)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }

因此,最后一步是通过将上面介绍的 Intl.Segmenter 与现在已经熟悉的 Intl.Collator ...[=27 相结合,实现满足 OP 最新要求的功能=]

function removeEveryMatchingNewLineStart(search, multilineString) {
  const letterSegmenter
    // - [ar]abic
    // - default grapheme granularity (locale comparable letters).
    = new Intl.Segmenter('ar'/*, { granularity: 'grapheme' }*/);

  const letterCollator
    // - [ar]abic
    // - base sensitivity
    //   ... Non-zero comparator result value for strings only
    //   that for a base letter comparison are considered unequal.
    = new Intl.Collator('ar', { sensitivity: 'base' } );

  const getLocaleComparableLetterList = str =>
    [...letterSegmenter.segment(str)].map(({ segment }) => segment);

  function replaceLineStartByBoundComparableLetters(line) {
    const searchLetters = this;
    let lineLetters = getLocaleComparableLetterList(line);

    if (searchLetters.every((searchLetter, idx/*, arr*/) =>
      (letterCollator.compare(searchLetter, lineLetters[idx]) === 0)
    )) {
      lineLetters = lineLetters.slice(searchLetters.length);

      let leadingBlanks = '';
      while (lineLetters[0] === ' ') {
        leadingBlanks = leadingBlanks + lineLetters.shift();
      }
      line = `${ lineLetters.join('') }${ leadingBlanks }`;

      // // due to keeping/restoring leading witespace sequences ...
      // // ... all the above additional computation instead of ...
      // // ... a simple ...
      // line = lineLetters.slice(searchLetters.length).join('')
    }
    return line;
  }
  return String(multilineString)
    .split(/(\n)/)
    .map(
      replaceLineStartByBoundComparableLetters.bind(
        getLocaleComparableLetterList(String(search))
      )
    )
    .join('');
}
const sampleData = `السلام عليكم ورحمة الله
السَلام عليكمُ ورحمةُ الله
أهلا ومرحبا
السَلامُ عَليكمُ ورَحمةُ الله
السَّلَامُ عَلَيْكُمُ وَرَحْمَةُ الله`;

console.log('sampleData ...', sampleData);
console.log(
  "removeEveryMatchingNewLineStart('السلام', sampleData) ...",
  removeEveryMatchingNewLineStart('السلام', sampleData)
);
console.log(
  "removeEveryMatchingNewLineStart('الس', sampleData) ...",
  removeEveryMatchingNewLineStart('الس', sampleData)
);
console.log(
  "removeEveryMatchingNewLineStart('السلام ', sampleData) ...",
  removeEveryMatchingNewLineStart('السلام ', sampleData)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }

我想出了以下可能的解决方案。

以下解决方案分为两部分;首先,函数 startsWithAr() 用于“部分”模仿 javascript startsWith() 方法,但用于阿拉伯字符串。

但是,不是 returning 'true''false',而是 return 源字符串开头的 index after the characters we are looking for(即长度在源字符串中找到的字符串,包括其 Tashkeel(变音符号),如果有的话),否则,如果在字符串的开头找不到指定字符串的字符,则它 returns -1

使用 startsWithAr() 函数,然后我们创建(在 第二部分 )一个函数,如果在字符串的开头找到,则删除指定字符串的字符使用 slice() 方法的源字符串; removeStartString() 函数。

这种方法不仅允许保留源字符串其余部分的 Tashkeel(变音符号),还允许搜索和删除带有 Tahmeez 的字符串。

该函数忽略 源字符串和 Look-For 搜索字符串中的 Tashkeel(变音符号)和 Tahmeez,并将 return 的剩余部分从源字符串的开头删除指定的字符开始后,源字符串及其原始 Tashkeel(变音符号)完好无损。

这样我们就可以使用该函数来处理阿拉伯文字中的所有 Unicode,而不是将其限制在定义的范围内,因为任何其他语言的任何其他字符都将被忽略。

我们还可以通过将“Ç”与“⑩”匹配来轻松改进它,这样我们就可以通过在 2 .replace()行。

我在下面包含了关于使用 startsWithAr() 函数和 removeStartString() 函数的单独测试用例。

如果需要,可以将这两个功能合并为一个功能。

请根据需要进行改进;任何建议表示赞赏。


第 1 部分:startsWithAr()


//=====================================================================
// startsWithAr() function
// Purpose:
// Determines whether an Arabic string (the "Source String") begins with the characters
// of a specified string (the "Look-For String").
// Return the position (index) after the Look-For String if found, else return -1 if not found.
// Ignores Tashkeel (diacritics) and Tahmeez in both the Source and Look-For Strings.
// The returned position index is zero based.
// By knowing the position (index) after the Look-For String, one can remove the
// starting string using the slice() method while maintaining the remainder of the Source String with
// its original tashkeel (diacritics) unchanged.
//
// Parameters:
// str     : The Source String to search in.
// lookFor : The characters to be searched for at the start of this string.
//=====================================================================
function startsWithAr(str,lookFor) {
let indexLookFor=0, tshk=/[ؐ-ًؕ-ٖٓ-ٟۖ-ٰٰۭـ]/, w=/[ؤ]/g,hamz=/[آأإٱٲٳٵ]/g;
lookFor=lookFor.replace(hamz,'ا').replace(w,'و').replace(/[ؐ-ًؕ-ٖٓ-ٟۖ-ٰٰۭـ]/g,''); // normalize the lookFor string
for (let indexStr=0; indexStr<str.length;indexStr++) {
while(tshk.test(str[indexStr])&&indexStr<str.length)++indexStr; // skip tashkeel & increase index
if (lookFor[indexLookFor]!==str[indexStr].replace(hamz,'ا').replace(w,'و')) return-1; // no match, so exit -1
indexLookFor++;                               // match found so next char in lookFor String
    if (indexLookFor>=lookFor.length) {       // if end of Source String then WE FOUND IT
      indexStr+=1;                            // point after source char
      while(tshk.test(str[indexStr])&&indexStr<str.length)++indexStr; // skip tashkeel after Source String if any
    return indexStr;      // return index in Source String after lookFor string and after any tashkeel
    }
}
return-1; // not found end of string reached
}
//=========================================
// test cases for startsWithAr() function
//=========================================
var r =0; // test tracking flag
r |= test("السلام عَلَيَكُمُ ورحمة الله","السلام",6);  // find the start letters 'السلام'
r |= test("الْسًّلامُ عَلَيَكُمُ ورحمة الله","السلام",10); // find the start letters 'السلام'
r |= test("الْسًّلامُ عَلَيَكُمُ وَرَحَمَةَ الله","السَّلام",10); // find the start letters 'السَّلام'
r |= test("ألْسًّلامُ عَلَيَكُمُ وَرَحَمَةَ الله","السَّلام",10); // find the start letters 'السَّلام'
r |= test("السؤال هو التالي","السوال",6);      // find the start letters 'السوال'
r |= test("السيد/علي","السيد",5);           // find the start letters 'السيد'
r |= test("السيد/علي","ف",-1);           // find the start letters 'السيد'
r |= test(" السيد"," ",1);               // find the start letter ' ' (space)
r |= test("المجد لنا","ال",2);             // find the start letters 'ال'
r |= test("المجد لنا","ا",1);              // find the start letter  'ا'
r |= test("ألمجد لنا","ال",2);             // find the start letters 'ال'
r |= test("إلمجد لنا","ال",2);             // find the start letters 'ال'
r |= test("إلمجد لنا","ألْ",2);             // find the start letters 'ألْ'
r |= test("إلْمَجد لَنا","ألْ",3);             // find the start letters 'ألْ'
r |= test("","ا",-1);                  // empty Source String
r |= test("","",-1);                  // empty Source String and Look-For String

if (r==0) console.log("✅ All startsWithAr() test cases passed");


//-----------------------------------
function test(str,lookfor,should) {
  let result= startsWithAr(str,lookfor);
  if (result !== should) {console.log(`
  ${str} Output   :${result}
  ${str} Should be:${should}
  `);return 1;}
  }


第二部分:removeStartString()


//=====================================================================
// removeStartString() function
// Purpose:
// Determines whether an Arabic string (the "Source String") begins with the characters
// of a specified string (the "Look-For String").
// If found the Look-For String is removed and the reminder of the Source String is returned
// with its original Tashkeel (diacritics);
// If no match then return original Source String.
//
// Ignores Tashkeel (diacritics) and Tahmeez in both the Source and Look-For Strings.
// The function uses the startsWithAr() function to determine the index after the matched
// starting string/characters.
//
// Parameters:
// str     : The Source String to search in.
// toRemove: The characters to be searched for and removed if at the start of this string.
//=====================================================================
function removeStartString(str,toRemove) {
let index=startsWithAr(str,toRemove);
if (index>-1) str=str.slice(index);
return str;
}

//=========================================
// test cases for removeStartString() function
//=========================================
var r =0; // test tracking flag
r |= test2("السلام عَلَيَكُمُ ورحمة الله","السلام"," عَلَيَكُمُ ورحمة الله");  // remove the start letters 'السلام'
r |= test2("ألْسًّلامُ عَلَيَكُمُ ورحمة الله","السلام"," عَلَيَكُمُ ورحمة الله");  // remove the start letters 'ألْسًّلامُ'
r |= test2("السلام عَلَيَكُمُ ورحمة الله","ألْسًّلامُ"," عَلَيَكُمُ ورحمة الله");  // remove the start letters 'ألْسًّلامُ'
r |= test2(" السلام عَلَيَكُمُ ورحمة الله"," ألْسًّلامُ"," عَلَيَكُمُ ورحمة الله");// remove the start letters 'ألْسًّلامُ '
r |= test2("السلام عَلَيَكُمُ ورحمة الله","ال","سلام عَلَيَكُمُ ورحمة الله"); // remove the start letters 'ال'
r |= test2("أَهْلًا وَسَهلًا","ا","هْلًا وَسَهلًا");             // remove the start letter 'ا'    r |= test2("أَهْلًا وَسَهلًا"," ","أَهْلًا وَسَهلًا");                // remove the start letter ' '
r |= test2("أَهْلًا وَسَهلًا","","أَهْلًا وَسَهلًا");             // remove the start letter ''
r |= test2("أَهْلًا وَسَهلًا","إلى","أَهْلًا وَسَهلًا");           // remove the start letters 'إلى'

if (r==0) console.log("✅ All removeStartString() test cases passed");

//-----------------------------------
function startsWithAr(str,lookFor) {
let indexLookFor=0, tshk=/[ؐ-ًؕ-ٖٓ-ٟۖ-ٰٰۭـ]/, w=/[ؤ]/g,hamz=/[آأإٱٲٳٵ]/g;
lookFor=lookFor.replace(hamz,'ا').replace(w,'و').replace(/[ؐ-ًؕ-ٖٓ-ٟۖ-ٰٰۭـ]/g,''); 
for (let indexStr=0; indexStr<str.length;indexStr++) {
while(tshk.test(str[indexStr])&&indexStr<str.length)++indexStr; 
if (lookFor[indexLookFor]!==str[indexStr].replace(hamz,'ا').replace(w,'و')) return-1;
indexLookFor++;                                           
    if (indexLookFor>=lookFor.length) {                    
      indexStr+=1;                                         
      while(tshk.test(str[indexStr])&&indexStr<str.length)++indexStr; 
    return indexStr;
    }
}
return-1;
}
//-----------------------------------
function test2(str,toRemove,should) {
  let result= removeStartString(str,toRemove);
  if (result !== should) {console.log(`
  ${str} Output   :${result}
  ${str} Should be:${should}
  `);return 1;}
  }