<?php
/**
* SeekQuarry/Yioop --
* Open Source Pure PHP Search Engine, Crawler, and Indexer
*
* Copyright (C) 2009 - 2026 Chris Pollett chris@pollett.org
*
* LICENSE:
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* @author Chris Pollett chris@pollett.org
* @license https://www.gnu.org/licenses/ GPL3
* @link https://www.seekquarry.com/
* @copyright 2009 - 2026
* @filesource
*/
namespace seekquarry\yioop\locale\ar\resources;
/**
* Arabic specific tokenization code. In particular, it has a stemmer,
* The stemmer is my stab at porting Ljiljana Dolamic (University of Neuchatel,
* www.unine.ch/info/clef/) C stemming algorithm:
* http://members.unine.ch/jacques.savoy/clef
* That algorithm maps all stems to ASCII. Instead, I tried to leave everything
* using Arabic characters.
*
* @author Chris Pollett
*/
class Tokenizer
{
/**
* Words we don't want to be stemmed
* @var array
*/
public static $no_stem_list = [];
/**
* A list of frequently occurring terms for this locale which should
* be excluded from certain kinds of queries
* @var array
*/
public static $stop_words = [
"ا", "أ", "،", "عشر", "عدد", "عدة","عشرة",
"عدم", "عام", "عاما", "عن", "عند", "عندما",
"على", "عليه", "عليها", "زيارة", "سنة", "سنوات",
"تم", "ضد", "بعد", "بعض", "اعادة", "اعلنت",
"بسبب", "حتى", "اذا", "احد", "اثر", "برس",
"باسم", "غدا", "شخصا", "صباح", "اطار",
"اربعة", "اخرى", "بان", "اجل", "غير",
"بشكل", "حاليا", "بن", "به", "ثم", "اف",
"ان", "او", "اي", "بها", "صفر", "حيث",
"اكد", "الا", "اما", "امس", "السابق",
"التى", "التي", "اكثر", "ايار", "ايضا",
"ثلاثة", "الذاتي", "الاخيرة", "الثاني",
"الثانية", "الذى", "الذي", "الان", "امام",
"ايام", "خلال", "حوالى", "الذين", "الاول",
"الاولى", "بين", "ذلك", "دون", "حول", "حين",
"الف", "الى", "انه", "اول", "ضمن", "انها",
"جميع", "الماضي", "الوقت", "المقبل", "اليوم",
"ـ", "ف", "و", "و6", "قد", "لا", "ما", "مع",
"مساء", "هذا", "واحد", "واضاف", "واضافت",
"فان", "قبل", "قال", "كان", "لدى", "نحو",
"هذه", "وان", "واكد", "كانت", "واوضح",
"مايو", "فى", "في", "كل", "لم", "لن", "له",
"من", "هو", "هي", "قوة", "كما", "لها", "منذ",
"وقد", "ولا", "نفسه", "لقاء", "مقابل", "هناك",
"وقال", "وكان", "نهاية", "وقالت", "وكانت",
"للامم", "فيه", "كلم", "لكن", "وفي", "وقف",
"ولم", "ومن", "وهو", "وهي", "يوم", "فيها",
"منها", "مليار", "لوكالة", "يكون", "يمكن",
"مليون"
];
/**
* Stub function which could be used for a word segmenter.
* Such a segmenter on input thisisabunchofwords would output
* this is a bunch of words
*
* @param string $pre_segment before segmentation
* @return string should return string with words separated by space
* in this case does nothing
*/
public static function segment($pre_segment)
{
return $pre_segment;
}
/**
* Removes the stop words from the page (used for Word Cloud generation)
*
* @param mixed $data either a string or an array of string to remove
* stop words from
* @return mixed $data with no stop words
*/
public static function stopwordsRemover($data)
{
static $pattern = "";
if (empty($pattern)) {
$pattern = '/\b(' . implode('|', self::$stop_words) . ')\b/u';
}
$data = preg_replace($pattern, '', $data);
return $data;
}
/**
* Computes the stem of an Arabic word
*
* @param string $word the string to stem
* @return string the stem of $word
*/
public static function stem($word)
{
if (in_array($word, self::$no_stem_list)) {
return $word;
}
$word = mb_strtolower($word);
$word = self::removeModifiersAndArchaic($word);
$word = self::removeSuffix($word);
$word = self::removePrefix($word);
return $word;
}
/**
* Removes common letter modifiers as well as some archaic characters
* @param string $word Arabic word to strip diacritic / tatweel /
* archaic-letter modifiers from
* @return string the $word after letter modifiers removed
*/
private static function removeModifiersAndArchaic($word)
{
$modifiers = ["\u0621", "\u0640", "\u064B", "\u064C", "\u064D",
"\u064E", "\u064F", "\u0650", "\u0651", "\u0652", "\u0653",
"\u0654", "\u0655", "\u0656", "\u0674", "\u0677", "\u0679"];
foreach ($modifiers as $modifier) {
$m = json_decode('"'.$modifier.'"');
$word = preg_replace("/".$m."/u", "", $word);
}
return $word;
}
/**
* Removes Arabic suffixes to get root
*
* @param string $word word to remove suffixes from
* @return string the $word after suffix removal
*/
private static function removeSuffix($word)
{
$length = mb_strlen($word);
if ($length > 5) {
$last_two = mb_substr($word, -2);
if (in_array($last_two,["ون", "آه", "أه", "آت", "أت", "آن",
"أن", "ىة", "ىه", "ىن", "ةئ", "ئن", "ئه"])) {
$word = mb_substr($word, 0, -2);
return $word;
}
}
if ($length > 4) {
$last_one = mb_substr($word, -1);
if (in_array($last_one, ["ى", "ۃ", "ه", "ئ"])) {
$word = mb_substr($word, 0, -1);
return $word;
}
}
return $word;
}
/**
* Removes Arabic prefixes to get root
* @param string $word word to remove prefixes from
*
* @return string the $word after prefix removal
*/
private static function removePrefix($word)
{
$length = mb_strlen($word);
if ($length > 6) {
$start_three = mb_substr($word, 0, 3);
if (in_array($start_three, ["ڡآل", "ۀآل", "بآل", "وآل",
"ڡأل", "ۀأل", "بأل", "وأل"])) {
$word = mb_substr($word, 3);
return $word;
}
}
if ($length > 5) {
$start_two = mb_substr($word, 0, 2);
if (in_array($start_two, ["آل", "أل"])) {
$word = mb_substr($word, 2);
return $word;
}
}
if ($length > 4) {
$start_one = mb_substr($word, 0, 1);
if (in_array($start_one, ["و", "ى", "ٸ"])) {
$word = mb_substr($word, 1);
return $word;
}
}
return $word;
}
}