I18N_Arabic
[ class tree: I18N_Arabic ] [ index: I18N_Arabic ] [ all elements ]

Source for file Query.php

Documentation is available at Query.php

  1. <?php
  2. /**
  3.  * ----------------------------------------------------------------------
  4.  *  
  5.  * Copyright (c) 2006-2016 Khaled Al-Sham'aa.
  6.  *  
  7.  * http://www.ar-php.org
  8.  *  
  9.  * PHP Version 5
  10.  *  
  11.  * ----------------------------------------------------------------------
  12.  *  
  13.  * LICENSE
  14.  *
  15.  * This program is open source product; you can redistribute it and/or
  16.  * modify it under the terms of the GNU Lesser General Public License (LGPL)
  17.  * as published by the Free Software Foundation; either version 3
  18.  * of the License, or (at your option) any later version.
  19.  *  
  20.  * This program is distributed in the hope that it will be useful,
  21.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  22.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  23.  * GNU Lesser General Public License for more details.
  24.  *  
  25.  * You should have received a copy of the GNU Lesser General Public License
  26.  * along with this program.  If not, see <http://www.gnu.org/licenses/lgpl.txt>.
  27.  *  
  28.  * ----------------------------------------------------------------------
  29.  *  
  30.  * Class Name: Arabic Queary Class
  31.  *  
  32.  * Filename: Query.php
  33.  *  
  34.  * Original  Author(s): Khaled Al-Sham'aa <khaled@ar-php.org>
  35.  *  
  36.  * Purpose:  Build WHERE condition for SQL statement using MySQL REGEXP and
  37.  *           Arabic lexical  rules
  38.  *            
  39.  * ----------------------------------------------------------------------
  40.  *  
  41.  * Arabic Queary Class
  42.  *
  43.  * PHP class build WHERE condition for SQL statement using MySQL REGEXP and
  44.  * Arabic lexical  rules.
  45.  *    
  46.  * With the exception of the Qur'an and pedagogical texts, Arabic is generally
  47.  * written without vowels or other graphic symbols that indicate how a word is
  48.  * pronounced. The reader is expected to fill these in from context. Some of the
  49.  * graphic symbols include sukuun, which is placed over a consonant to indicate that
  50.  * it is not followed by a vowel; shadda, written over a consonant to indicate it is
  51.  * doubled; and hamza, the sign of the glottal stop, which can be written above or
  52.  * below (alif) at the beginning of a word, or on (alif), (waaw), (yaa'),
  53.  * or by itself on the line elsewhere. Also, common spelling differences regularly
  54.  * appear, including the use of (haa') for (taa' marbuuta) and (alif maqsuura)
  55.  * for (yaa'). These features of written Arabic, which are also seen in Hebrew as
  56.  * well as other languages written with Arabic script (such as Farsi, Pashto, and
  57.  * Urdu), make analyzing and searching texts quite challenging. In addition, Arabic
  58.  * morphology and grammar are quite rich and present some unique issues for
  59.  * information retrieval applications.
  60.  * 
  61.  * There are essentially three ways to search an Arabic text with Arabic queries:
  62.  * literal, stem-based or root-based.
  63.  * 
  64.  * A literal search, the simplest search and retrieval method, matches documents
  65.  * based on the search terms exactly as the user entered them. The advantage of this
  66.  * technique is that the documents returned will without a doubt contain the exact
  67.  * term for which the user is looking. But this advantage is also the biggest
  68.  * disadvantage: many, if not most, of the documents containing the terms in
  69.  * different forms will be missed. Given the many ambiguities of written Arabic, the
  70.  * success rate of this method is quite low. For example, if the user searches
  71.  * for (kitaab, book), he or she will not find documents that only
  72.  * contain (`al-kitaabu, the book).
  73.  * 
  74.  * Stem-based searching, a more complicated method, requires some normalization of
  75.  * the original texts and the queries. This is done by removing the vowel signs,
  76.  * unifying the hamza forms and removing or standardizing the other signs.
  77.  * Additionally, grammatical affixes and other constructions which attach directly
  78.  * to words, such as conjunctions, prepositions, and the definite article, should be
  79.  * identified and removed. Finally, regular and irregular plural forms need to be
  80.  * identified and reduced to their singular forms. Performing this type of stemming
  81.  * leads to more successful searches, but can be problematic due to over-generation
  82.  * or incorrect generation of stems.
  83.  * 
  84.  * A third method for searching Arabic texts is to index and search for the root
  85.  * forms of each word. Since most verbs and nouns in Arabic are derived from
  86.  * triliteral (or, rarely, quadriliteral) roots, identifying the underlying root of
  87.  * each word theoretically retrieves most of the documents containing a given search
  88.  * term regardless of form. However, there are some significant challenges with this
  89.  * approach. Determining the root for a given word is extremely difficult, since it
  90.  * requires a detailed morphological, syntactic and semantic analysis of the text to
  91.  * fully disambiguate the root forms. The issue is complicated further by the fact
  92.  * that not all words are derived from roots. For example, loan words (words
  93.  * borrowed from another language) are not based on root forms, although there are
  94.  * even exceptions to this rule. For example, some loans that have a structure
  95.  * similar to triliteral roots, such as the English word film, are handled
  96.  * grammatically as if they were root-based, adding to the complexity of this type
  97.  * of search. Finally, the root can serve as the foundation for a wide variety of
  98.  * words with related meanings. The root (k-t-b) is used for many words related
  99.  * to writing, including (kataba, to write), (kitaab, book), (maktab,
  100.  * office), and (kaatib, author). But the same root is also used for regiment/
  101.  * battalion, (katiiba). As a result, searching based on root forms results in
  102.  * very high recall, but precision is usually quite low.
  103.  * 
  104.  * While search and retrieval of Arabic text will never be an easy task, relying on
  105.  * linguistic analysis tools and methods can help make the process more successful.
  106.  * Ultimately, the search method you choose should depend on how critical it is to
  107.  * retrieve every conceivable instance of a word or phrase and the resources you
  108.  * have to process search returns in order to determine their true relevance.
  109.  * 
  110.  * Source: Volume 13 Issue 7 of MultiLingual Computing &
  111.  * Technology published by MultiLingual Computing, Inc., 319 North First Ave.,
  112.  * Sandpoint, Idaho, USA, 208-263-8178, Fax: 208-263-6310.
  113.  * 
  114.  * Example:
  115.  * <code>
  116.  *     include('./I18N/Arabic.php');
  117.  *     $obj = new I18N_Arabic('Query');
  118.  *     
  119.  *     $dbuser = 'root';
  120.  *     $dbpwd = '';
  121.  *     $dbname = 'test';
  122.  *     
  123.  *     try {
  124.  *         $dbh = new PDO('mysql:host=localhost;dbname='.$dbname, $dbuser, $dbpwd);
  125.  * 
  126.  *         // Set the error reporting attribute
  127.  *         $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  128.  * 
  129.  *         $dbh->exec("SET NAMES 'utf8'");
  130.  *     
  131.  *         if ($_GET['keyword'] != '') {
  132.  *             $keyword = @$_GET['keyword'];
  133.  *             $keyword = str_replace('\"', '"', $keyword);
  134.  *     
  135.  *             $obj->setStrFields('headline');
  136.  *             $obj->setMode($_GET['mode']);
  137.  *     
  138.  *             $strCondition = $Arabic->getWhereCondition($keyword);
  139.  *         } else {
  140.  *             $strCondition = '1';
  141.  *         }
  142.  *     
  143.  *         $StrSQL = "SELECT `headline` FROM `aljazeera` WHERE $strCondition";
  144.  * 
  145.  *         $i = 0;
  146.  *         foreach ($dbh->query($StrSQL) as $row) {
  147.  *             $headline = $row['headline'];
  148.  *             $i++;
  149.  *             if ($i % 2 == 0) {
  150.  *                 $bg = "#f0f0f0";
  151.  *             } else {
  152.  *                 $bg = "#ffffff";
  153.  *             }
  154.  *             echo "<tr bgcolor=\"$bg\"><td>$headline</td></tr>";
  155.  *         }
  156.  * 
  157.  *         // Close the databse connection
  158.  *         $dbh = null;
  159.  * 
  160.  *     } catch (PDOException $e) {
  161.  *         echo $e->getMessage();
  162.  *     }
  163.  * </code>
  164.  * 
  165.  * @category  I18N
  166.  * @package   I18N_Arabic
  167.  * @author    Khaled Al-Sham'aa <khaled@ar-php.org>
  168.  * @copyright 2006-2016 Khaled Al-Sham'aa
  169.  *    
  170.  * @license   LGPL <http://www.gnu.org/licenses/lgpl.txt>
  171.  * @link      http://www.ar-php.org
  172.  */
  173.  
  174. /**
  175.  * This PHP class build WHERE condition for SQL statement using MySQL REGEXP and
  176.  * Arabic lexical  rules
  177.  *  
  178.  * @category  I18N
  179.  * @package   I18N_Arabic
  180.  * @author    Khaled Al-Sham'aa <khaled@ar-php.org>
  181.  * @copyright 2006-2016 Khaled Al-Sham'aa
  182.  *    
  183.  * @license   LGPL <http://www.gnu.org/licenses/lgpl.txt>
  184.  * @link      http://www.ar-php.org
  185.  */ 
  186. {
  187.     private $_fields          array();
  188.     private $_lexPatterns     array();
  189.     private $_lexReplacements array();
  190.     private $_mode            0;
  191.  
  192.     /**
  193.      * Loads initialize values
  194.      */         
  195.     public function __construct()
  196.     {
  197.         $xml simplexml_load_file(dirname(__FILE__).'/data/ArQuery.xml')
  198.          
  199.         foreach ($xml->xpath("//preg_replace[@function='__construct']/pair")
  200.                  as $pair
  201.  
  202.                  array_push($this->_lexPatterns(string)$pair->search)
  203.             array_push($this->_lexReplacements(string)$pair->replace)
  204.         }
  205.     }
  206.     
  207.     /**
  208.      * Setting value for $_fields array
  209.      *      
  210.      * @param array $arrConfig Name of the fields that SQL statement will search
  211.      *                          them (in array format where items are those
  212.      *                          fields names)
  213.      *                       
  214.      * @return object $this to build a fluent interface
  215.      * @author Khaled Al-Sham'aa <khaled@ar-php.org>
  216.      */
  217.     public function setArrFields($arrConfig)
  218.     {
  219.         if (is_array($arrConfig)) {
  220.             // Get _fields array
  221.             $this->_fields $arrConfig;
  222.         }
  223.         
  224.         return $this;
  225.     }
  226.     
  227.     /**
  228.      * Setting value for $_fields array
  229.      *      
  230.      * @param string $strConfig Name of the fields that SQL statement will search
  231.      *                           them (in string format using comma as delimated)
  232.      *                          
  233.      * @return object $this to build a fluent interface
  234.      * @author Khaled Al-Sham'aa <khaled@ar-php.org>
  235.      */
  236.     public function setStrFields($strConfig)
  237.     {
  238.         if (is_string($strConfig)) {
  239.             // Get _fields array
  240.             $this->_fields explode(','$strConfig);
  241.         }
  242.  
  243.         return $this;
  244.     }
  245.     
  246.     /**
  247.      * Setting $mode propority value that refer to search mode
  248.      * [0 for OR logic | 1 for AND logic]
  249.      *      
  250.      * @param integer $mode Setting value to be saved in the $mode propority
  251.      *      
  252.      * @return object $this to build a fluent interface
  253.      * @author Khaled Al-Sham'aa <khaled@ar-php.org>
  254.      */
  255.     public function setMode($mode)
  256.     {
  257.         if (in_array($modearray('0''1'))) {
  258.             // Set search mode [0 for OR logic | 1 for AND logic]
  259.             $this->_mode $mode;
  260.         }
  261.         
  262.         return $this;
  263.     }
  264.     
  265.     /**
  266.      * Getting $mode propority value that refer to search mode
  267.      * [0 for OR logic | 1 for AND logic]
  268.      *      
  269.      * @return integer Value of $mode properity
  270.      * @author Khaled Al-Sham'aa <khaled@ar-php.org>
  271.      */
  272.     public function getMode()
  273.     {
  274.         // Get search mode value [0 for OR logic | 1 for AND logic]
  275.         return $this->_mode;
  276.     }
  277.     
  278.     /**
  279.      * Getting values of $_fields Array in array format
  280.      *      
  281.      * @return array Value of $_fields array in Array format
  282.      * @author Khaled Al-Sham'aa <khaled@ar-php.org>
  283.      */
  284.     public function getArrFields()
  285.     {
  286.         $fields $this->_fields;
  287.         
  288.         return $fields;
  289.     }
  290.     
  291.     /**
  292.      * Getting values of $_fields array in String format (comma delimated)
  293.      *      
  294.      * @return string Values of $_fields array in String format (comma delimated)
  295.      * @author Khaled Al-Sham'aa <khaled@ar-php.org>
  296.      */
  297.     public function getStrFields()
  298.     {
  299.         $fields implode(','$this->_fields);
  300.         
  301.         return $fields;
  302.     }
  303.     
  304.     /**
  305.      * Build WHERE section of the SQL statement using defind lex's rules, search
  306.      * mode [AND | OR], and handle also phrases (inclosed by "") using normal
  307.      * LIKE condition to match it as it is.
  308.      *      
  309.      * @param string $arg String that user search for in the database table
  310.      *                    
  311.      * @return string The WHERE section in SQL statement
  312.      *                 (MySQL database engine format)
  313.      * @author Khaled Al-Sham'aa <khaled@ar-php.org>
  314.      */
  315.     public function getWhereCondition($arg)
  316.     {
  317.         $sql '';
  318.         
  319.         //$arg = mysql_real_escape_string($arg);
  320.         $search array("\\",  "\x00""\n",  "\r",  "'",  '"'"\x1a");
  321.         $replace array("\\\\","\\0","\\n""\\r""\'"'\"'"\\Z");
  322.         $arg str_replace($search$replace$arg);
  323.         
  324.         // Check if there are phrases in $arg should handle as it is
  325.         $phrase explode("\""$arg);
  326.         
  327.         if (count($phrase2{
  328.             // Re-init $arg variable
  329.             // (It will contain the rest of $arg except phrases).
  330.             $arg '';
  331.             
  332.             for ($i 0$i count($phrase)$i++{
  333.                 $subPhrase $phrase[$i]
  334.                 if ($i == && $subPhrase != ''{
  335.                     // Re-build $arg variable after restricting phrases
  336.                     $arg .= $subPhrase;
  337.                 elseif ($i == && $subPhrase != ''{
  338.                     // Handle phrases using reqular LIKE matching in MySQL
  339.                     $this->wordCondition[$this->getWordLike($subPhrase);
  340.                 }
  341.             }
  342.         }
  343.         
  344.         // Handle normal $arg using lex's and regular expresion
  345.         $words preg_split('/\s+/'trim($arg));
  346.         
  347.         foreach ($words as $word{
  348.             //if (is_numeric($word) || strlen($word) > 2) {
  349.                 // Take off all the punctuation
  350.                 //$word = preg_replace("/\p{P}/", '', $word);
  351.                 $exclude array('('')''['']''{''}'','';'':'
  352.                                  '?''!''،''؛''؟');
  353.                 $word    str_replace($exclude''$word);
  354.  
  355.                 $this->wordCondition[$this->getWordRegExp($word);
  356.             //}
  357.         }
  358.         
  359.         if (!empty($this->wordCondition)) {
  360.             if ($this->_mode == 0{
  361.                 $sql '(' implode(') OR ('$this->wordCondition')';
  362.             elseif ($this->_mode == 1{
  363.                 $sql '(' implode(') AND ('$this->wordCondition')';
  364.             }
  365.         }
  366.         
  367.         return $sql;
  368.     }
  369.     
  370.     /**
  371.      * Search condition in SQL format for one word in all defind fields using
  372.      * REGEXP clause and lex's rules
  373.      *      
  374.      * @param string $arg String (one word) that you want to build a condition for
  375.      *      
  376.      * @return string sub SQL condition (for internal use)
  377.      * @author Khaled Al-Sham'aa <khaled@ar-php.org>
  378.      */
  379.     protected function getWordRegExp($arg)
  380.     {
  381.         $arg $this->lex($arg);
  382.         //$sql = implode(" REGEXP '$arg' OR ", $this->_fields) . " REGEXP '$arg'";
  383.         $sql ' REPLACE(' 
  384.                implode(", 'ـ', '') REGEXP '$arg' OR REPLACE("$this->_fields
  385.                ", 'ـ', '') REGEXP '$arg'";
  386.  
  387.         
  388.         return $sql;
  389.     }
  390.     
  391.     /**
  392.      * Search condition in SQL format for one word in all defind fields using
  393.      * normal LIKE clause
  394.      *      
  395.      * @param string $arg String (one word) that you want to build a condition for
  396.      *      
  397.      * @return string sub SQL condition (for internal use)
  398.      * @author Khaled Al-Sham'aa <khaled@ar-php.org>
  399.      */
  400.     protected function getWordLike($arg)
  401.     {
  402.         $sql implode(" LIKE '$arg' OR "$this->_fields" LIKE '$arg'";
  403.         
  404.         return $sql;
  405.     }
  406.     
  407.     /**
  408.      * Get more relevant order by section related to the user search keywords
  409.      *      
  410.      * @param string $arg String that user search for in the database table
  411.      *                    
  412.      * @return string sub SQL ORDER BY section
  413.      * @author Saleh AlMatrafe <saleh@saleh.cc>
  414.      */
  415.     public function getOrderBy($arg)
  416.     {
  417.         // Check if there are phrases in $arg should handle as it is
  418.         $phrase explode("\""$arg);
  419.         if (count($phrase2{
  420.             // Re-init $arg variable 
  421.             // (It will contain the rest of $arg except phrases).
  422.             $arg '';
  423.             for ($i 0$i count($phrase)$i++{
  424.                 if ($i == && $phrase[$i!= ''{
  425.                     // Re-build $arg variable after restricting phrases
  426.                     $arg .= $phrase[$i];
  427.                 elseif ($i == && $phrase[$i!= ''{
  428.                     // Handle phrases using reqular LIKE matching in MySQL
  429.                     $wordOrder[$this->getWordLike($phrase[$i]);
  430.                 }
  431.             }
  432.         }
  433.         
  434.         // Handle normal $arg using lex's and regular expresion
  435.         $words explode(' '$arg);
  436.         foreach ($words as $word{
  437.             if ($word != ''{
  438.                 $wordOrder['CASE WHEN ' 
  439.                                $this->getWordRegExp($word
  440.                                ' THEN 1 ELSE 0 END';
  441.             }
  442.         }
  443.         
  444.         $order '((' implode(') + ('$wordOrder')) DESC';
  445.         
  446.         return $order;
  447.     }
  448.  
  449.     /**
  450.      * This method will implement various regular expressin rules based on
  451.      * pre-defined Arabic lexical rules
  452.      *      
  453.      * @param string $arg String of one word user want to search for
  454.      *      
  455.      * @return string Regular Expression format to be used in MySQL query statement
  456.      * @author Khaled Al-Sham'aa <khaled@ar-php.org>
  457.      */
  458.     protected function lex($arg)
  459.     {
  460.         $arg preg_replace($this->_lexPatterns$this->_lexReplacements$arg);
  461.         
  462.         return $arg;
  463.     }
  464.     
  465.     /**
  466.      * Get most possible Arabic lexical forms for a given word
  467.      *      
  468.      * @param string $word String that user search for
  469.      *      
  470.      * @return string list of most possible Arabic lexical forms for a given word
  471.      * @author Khaled Al-Sham'aa <khaled@ar-php.org>
  472.      */
  473.     protected function allWordForms($word
  474.     {
  475.         $wordForms array($word);
  476.         
  477.         $postfix1 array('كم''كن''نا''ها''هم''هن');
  478.         $postfix2 array('ين''ون''ان''ات''وا');
  479.         
  480.         $len mb_strlen($word);
  481.  
  482.         if (mb_substr($word02== 'ال'{
  483.             $word mb_substr($word2);
  484.         }
  485.         
  486.         $wordForms[$word;
  487.  
  488.         $str1 mb_substr($word0-1);
  489.         $str2 mb_substr($word0-2);
  490.         $str3 mb_substr($word0-3);
  491.  
  492.         $last1 mb_substr($word-1);
  493.         $last2 mb_substr($word-2);
  494.         $last3 mb_substr($word-3);
  495.         
  496.         if ($len >= && $last3 == 'تين'{
  497.             $wordForms[$str3;
  498.             $wordForms[$str3 'ة';
  499.             $wordForms[$word 'ة';
  500.         }
  501.         
  502.         if ($len >= && ($last3 == 'كما' || $last3 == 'هما')) {
  503.             $wordForms[$str3;
  504.             $wordForms[$str3 'كما';
  505.             $wordForms[$str3 'هما';
  506.         }
  507.  
  508.         if ($len >= && in_array($last2$postfix2)) {
  509.             $wordForms[$str2;
  510.             $wordForms[$str2.'ة';
  511.             $wordForms[$str2.'تين';
  512.  
  513.             foreach ($postfix2 as $postfix{
  514.                 $wordForms[$str2 $postfix;
  515.             }
  516.         }
  517.  
  518.         if ($len >= && in_array($last2$postfix1)) {
  519.             $wordForms[$str2;
  520.             $wordForms[$str2.'ي';
  521.             $wordForms[$str2.'ك';
  522.             $wordForms[$str2.'كما';
  523.             $wordForms[$str2.'هما';
  524.  
  525.             foreach ($postfix1 as $postfix{
  526.                 $wordForms[$str2 $postfix;
  527.             }
  528.         }
  529.  
  530.         if ($len >= && $last2 == 'ية'{
  531.             $wordForms[$str1;
  532.             $wordForms[$str2;
  533.         }
  534.  
  535.         if (($len >= && ($last1 == 'ة' || $last1 == 'ه' || $last1 == 'ت')) 
  536.             || ($len >= && $last2 == 'ات')
  537.         {
  538.             $wordForms[$str1;
  539.             $wordForms[$str1 'ة';
  540.             $wordForms[$str1 'ه';
  541.             $wordForms[$str1 'ت';
  542.             $wordForms[$str1 'ات';
  543.         }
  544.         
  545.         if ($len >= && $last1 == 'ى'{
  546.             $wordForms[$str1 'ا';
  547.         }
  548.  
  549.         $trans array('أ' => 'ا''إ' => 'ا''آ' => 'ا');
  550.         foreach ($wordForms as $word{
  551.             $normWord strtr($word$trans);
  552.             if ($normWord != $word{
  553.                 $wordForms[$normWord;
  554.             }
  555.         }
  556.         
  557.         $wordForms array_unique($wordForms);
  558.         
  559.         return $wordForms;
  560.     }
  561.     
  562.     /**
  563.      * Get most possible Arabic lexical forms of user search keywords
  564.      *      
  565.      * @param string $arg String that user search for
  566.      *                    
  567.      * @return string list of most possible Arabic lexical forms for given keywords
  568.      * @author Khaled Al-Sham'aa <khaled@ar-php.org>
  569.      */
  570.     public function allForms($arg)
  571.     {
  572.         $wordForms array();
  573.         $words     explode(' '$arg);
  574.         
  575.         foreach ($words as $word{
  576.             $wordForms array_merge($wordForms$this->allWordForms($word));
  577.         }
  578.         
  579.         $str implode(' '$wordForms);
  580.         
  581.         return $str;
  582.     }
  583. }

Documentation generated on Fri, 01 Jan 2016 10:26:18 +0200 by phpDocumentor 1.4.0