Advertisement
Guest User

lemmatizer

a guest
Jan 18th, 2013
803
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 60.31 KB | None | 0 0
  1. <?php
  2.  
  3. class Lemmatizer {
  4.  
  5.     /*
  6.     ********************************************************************************
  7.     ***     ATTRIBUTES
  8.     ********************************************************************************
  9.     */
  10.  
  11.     /*
  12.         Regular expression for vowels
  13.  
  14.         @const
  15.         @var string
  16.     */
  17.     const VOWEL = "[aiueo]";
  18.  
  19.     /*
  20.         Regular expression for consonants
  21.  
  22.         @const
  23.         @var string
  24.     */
  25.     const CONSONANT = "[bcdfghjklmnpqrstvwxyz]";
  26.  
  27.     /*
  28.         Regular expression for alphabet, including a stripe in between
  29.         for pluralized or repetitive form
  30.  
  31.         @const
  32.         @var string
  33.     */
  34.     const ALPHA = "[a-z]+-?[a-z]*";
  35.  
  36.     /*
  37.         Holds the removed suffixes/prefixes for backtracking procedure
  38.  
  39.         @var array list of strings
  40.     */
  41.     protected $removed = array(
  42.         'particle' => '',
  43.         'possessive_pronoun' => '',
  44.         'derivational_suffix' => '',
  45.         'derivational_prefix' => ''
  46.     );
  47.  
  48.     /*
  49.         Serves as a container for successful dictionary lookup
  50.  
  51.         @var string
  52.     */
  53.     protected $found = null;
  54.  
  55.     /*
  56.         Tracks all the changes made to the word; The array is indexed by
  57.         the general prefix form, such as di,ke,se,be,me,pe,te.
  58.  
  59.         for example, the word 'menapak' undergoes transformation men-tapak.
  60.         the variable's structure would be:
  61.         ["me"] => (
  62.                 ["men"] => "t"
  63.             )
  64.  
  65.         @var array
  66.     */
  67.     public $complex_prefix_tracker = array();
  68.  
  69.     /*
  70.         Saves recoding path for corresponding rules; the array is indexed by
  71.         the general prefix form.
  72.  
  73.         same as prefix tracker variable; this variable is structured as:
  74.         ["me"] => (
  75.                 ["me"] => "n"
  76.             )
  77.  
  78.         @var array
  79.     */
  80.     public $recoding_tracker = array();
  81.  
  82.     /*
  83.         Serves as the error indicator if a termination condition occurs.
  84.         The conditions are:
  85.             > 'disallowed_pairs':   the identified prefix forms a disallowed
  86.                                     affix combination with suffix that was
  87.                                     removed in previous steps.
  88.             > 'lemma_not_found'     the lemmatizer fails to detect input word.
  89.  
  90.         @var string
  91.     */
  92.     public $error = null;
  93.  
  94.     /*
  95.         Records how many lookups performed
  96.  
  97.         @var integer
  98.     */
  99.     public $total_lookup = 0;
  100.  
  101.  
  102.     /*
  103.         Saves connection string to MySQL.
  104.         host: localhost
  105.         user: root
  106.         pass: <none>
  107.        
  108.         @var PDO Connection
  109.     */
  110.     protected $database;
  111.  
  112.  
  113.     private $time;
  114.  
  115.     /*
  116.     ********************************************************************************
  117.     ***     METHODS
  118.     ********************************************************************************
  119.     */
  120.  
  121.     /*
  122.        
  123.         Opens new database connection on instance construction
  124.  
  125.     */
  126.     public function __construct() {
  127.  
  128.         $this->database = new PDO("mysql:host=localhost;dbname=lemmatizer", "root", "");
  129.  
  130.         $this->time = microtime(true);
  131.  
  132.     }
  133.  
  134.     /*
  135.         Checks the input word against the dictionary; returns the word if found,
  136.         or returns false if not found
  137.  
  138.         @param string $word
  139.         @return mixed
  140.     */
  141.     protected function lookup($word) {
  142.  
  143.         // If the input word's length is smaller then 3, don't bother.
  144.         if(strlen($word)<3) return false;
  145.  
  146.  
  147.         /*
  148.             Saves input word for further processing
  149.  
  150.             @var string
  151.         */
  152.         $check = $word;
  153.  
  154.         $check2 = "";
  155.  
  156.         /*
  157.             Saves query result from PDO Query
  158.  
  159.             @var string
  160.         */
  161.         $query_string;
  162.  
  163.         /*
  164.            
  165.             Checks for repeated form that represents pluralized form;
  166.             for example 'buku-buku'
  167.  
  168.         */
  169.         if(preg_match("/^([a-z]+)-([a-z]+)$/", $check, $match)) {
  170.  
  171.             if($match[1] == $match[2]) {
  172.  
  173.                 $check = $match[1];
  174.                 $check2 = $word;
  175.  
  176.             }
  177.  
  178.         }
  179.  
  180.         /*
  181.                
  182.                 Attempts to programatically split joined words, in order to produce lemmas with
  183.                 more than one word. The split method only works when the first word contains 2 syllables.
  184.  
  185.         */
  186.         if(strlen($word) <= 6) {
  187.  
  188.             // executes lemma from database
  189.             $query_string = "'$check'";
  190.        
  191.         }
  192.         else {
  193.  
  194.             // regex string for a valid Indonesian syllable
  195.             $syllable = "([bcdfghjklmnpqrstvwxyz]|sy)?([aiueo])(?U)([bcdfghjklmnpqrstvwxyz]|ng)?";
  196.            
  197.             // regex string for identifying the two words.
  198.             $reg = "/^(?<first>aneka|({$syllable}{$syllable}))(?<second>{$syllable}{$syllable}(?U)({$syllable})*)$/";
  199.            
  200.             if(preg_match($reg, $word, $match)) {
  201.  
  202.                 // Performs query via PDO
  203.                 $query_string = "'".$match['first']." ".$match['second']."' OR lemma LIKE '$check'";
  204.  
  205.             } else {
  206.  
  207.                 $query_string = "'$check'";
  208.  
  209.             }
  210.         }
  211.  
  212.         if($check2!="") {
  213.  
  214.             $query_string .= " OR lemma LIKE '$check2'";
  215.  
  216.         }
  217.        
  218.         /*
  219.  
  220.             If the checked word is ended with a vowel and the removed derivational suffix is -kan,
  221.             there is a likely chance of overstemming; that is why the algorithm will check for
  222.             both possibilities; with -k or without -k, sorted by its PART OF SPEECH (verb prioritized)  
  223.  
  224.         */
  225.         if(preg_match('/[aiueo]$/', $word) && $this->removed['derivational_suffix']=='kan' && strlen($word)>3) {
  226.  
  227.             $query_string .= " OR lemma LIKE '{$check}k' ORDER BY pos DESC";
  228.         }
  229.  
  230.         /*
  231.             Executes lookup to database.
  232.  
  233.             @var PDO Object
  234.         */
  235.         $query = $this->database->query("SELECT * FROM dictionary WHERE lemma LIKE $query_string LIMIT 1");
  236.  
  237.         // updates total dictionary lookup counter
  238.         $this->total_lookup++;
  239.  
  240.         if($row = $query->fetch()) {
  241.  
  242.             // updates class property
  243.             $this->found = $row['lemma'];
  244.  
  245.             // returns result to function caller
  246.             return $this->found;
  247.  
  248.         }
  249.  
  250.     }
  251.  
  252.  
  253.     /*
  254.         Checks input word for rule precedence; If the input word has a confix:
  255.         be - lah, be - an, me - i, di - i, pe - i, te - i
  256.         Then, derivational prefix removal will be performed first
  257.  
  258.         @param string $word
  259.         @return boolean
  260.     */
  261.     protected function check_rule_precedence($word) {
  262.  
  263.         /*
  264.             Loads normalized alphabet regex (including stripes) from class' [constant];
  265.             for shorthand purposes.
  266.  
  267.             @var string
  268.         */
  269.         $alpha = self::ALPHA;
  270.  
  271.         /*
  272.             Regular expression for affix pairs:
  273.             ber - lah
  274.             ber - an
  275.             me - i
  276.             di - i
  277.             pe - i
  278.             ter - i
  279.  
  280.             @var array list of strings
  281.         */
  282.         $patterns = array(
  283.                 0 => "/^be(?<word>{$alpha})([^k]an|lah)$/",
  284.                 1 => "/^(me|di|pe|te)(?<word>{$alpha})(i)$/",
  285.                 2 => "/^(k|s)e(?<word>{$alpha})(i|kan)$/",
  286.                 3 => "/^(me|di|te|pe)(?<word>{$alpha})(an)$/",
  287.                 4 => "/^pe(?<word>{$alpha}(tah|[^k]an))/"
  288.             );
  289.  
  290.         /*
  291.  
  292.             Checks whether the input word matches the affix pairs above;
  293.             returns true if pattern is found, and false if not found
  294.  
  295.         */
  296.         foreach($patterns as $pattern) {
  297.  
  298.             if(preg_match($pattern, $word, $match)
  299.                 && $match['word'] != 'ngalam') return true;
  300.  
  301.         }
  302.  
  303.         return false;
  304.  
  305.     }
  306.  
  307.  
  308.     /*
  309.         Checks whether the input word contains disallowed affix pairs/confixes;
  310.         returns true if the word has disallowed pair
  311.  
  312.         @return boolean
  313.     */
  314.     protected function has_disallowed_pairs() {
  315.  
  316.         /*
  317.             Loads normalized alphabet regex (including stripes) from class' [constant];
  318.             for shorthand purposes.
  319.  
  320.             @var string
  321.         */
  322.         $alpha = self::ALPHA;
  323.  
  324.         /*
  325.             Regular expression for disallowed affix pairs:
  326.             be - i
  327.             ke - i and kan
  328.             se - i and kan
  329.             di - an
  330.             te - an
  331.  
  332.             @var array list of strings
  333.         */
  334.         $patterns = array(
  335.             0 => "/^be[^r]i$/",
  336.             1 => "/^(k|s)e(i|kan)$/",
  337.             2 => "/^(di|me|te)[^krwylp]an$/"
  338.         );
  339.  
  340.         /*
  341.  
  342.             Checks whether the identified derivational prefix and suffix matches the
  343.             affix pairs above; returns true if pattern is found, and false if not found
  344.  
  345.         */
  346.         if($this->removed["derivational_prefix"]!="" && $this->removed["derivational_suffix"]!="") {
  347.  
  348.             $prefix = reset($this->removed["derivational_prefix"]);
  349.  
  350.             foreach($patterns as $pattern) {
  351.  
  352.                 if(preg_match($pattern, $prefix . $this->removed["derivational_suffix"])) {
  353.  
  354.                     return true;
  355.                 }
  356.             }
  357.         }
  358.        
  359.         // no disallowed pairs found, good to go    
  360.         return false;
  361.  
  362.     }
  363.  
  364.  
  365.     /*
  366.         Attempts to remove inflectional suffixes:
  367.         (particles) -kah, -lah, -tah, -pun and (possessive pronoun) -ku, -mu, -nya
  368.         from input word; Returns original value if no inflectional suffix found
  369.  
  370.         @param string $word
  371.         @return string
  372.     */
  373.     protected function delete_inflectional_suffix($word) {
  374.  
  375.         /*
  376.             Holds the value after suffix removal process
  377.  
  378.             @var string
  379.         */
  380.         $result = $word;
  381.  
  382.         /*
  383.             Regular expression for Particle suffixes: (-kah, -lah, -tah, -pun)
  384.             and Possessive Pronoun suffixes (-ku, -mu, -nya)
  385.  
  386.             @var array
  387.         */
  388.         $patterns = array(
  389.                 'particle' => "/([klt]ah|pun)$/",
  390.                 'possessive_pronoun' => "/([km]u|nya)$/"
  391.             );
  392.  
  393.         /*
  394.  
  395.             Checks whether the input word contains inflectional suffix, with
  396.             additional handling for Particle endings; because inflectional suffix
  397.             can be stacked, e.g. "mobilnyapun"
  398.  
  399.         */
  400.         foreach($patterns as $key => $pattern) {
  401.  
  402.             if(preg_match($pattern, $result, $match)) {
  403.  
  404.                 $result = preg_replace($pattern, '', $result);
  405.  
  406.                 // Updates the removed value holder
  407.                 $this->removed[$key] = $match[0];
  408.  
  409.                 // Perform database lookup
  410.                 $check = $this->lookup($result);
  411.  
  412.                 // If a lemma is successfully found, return it.
  413.                 if($check) return $check;
  414.  
  415.             }
  416.  
  417.         }
  418.  
  419.         // returns the suffix removal result
  420.         return $result;
  421.  
  422.     }
  423.  
  424.  
  425.     /*
  426.         Attempts to remove derivational suffixes -i, -kan, -an from input word;
  427.         Returns original value if no derivational suffix found
  428.  
  429.         @param string $word
  430.         @return string
  431.     */
  432.     protected function delete_derivational_suffix($word) {
  433.  
  434.         /*
  435.             Holds the value after suffix removal process
  436.  
  437.             @var string
  438.         */
  439.         $result = $word;
  440.  
  441.         /*
  442.             Regular expression for derivational suffixes: -i, -kan, an
  443.  
  444.             @var string
  445.         */
  446.         $derivational_suffix = "/(i|k?an)$/";
  447.  
  448.         /*
  449.  
  450.             Checks whether input word contains derivational suffix; before
  451.             stripping the suffix, an additional check for disallowed affix pair
  452.             is performed
  453.  
  454.         */
  455.         if(preg_match($derivational_suffix, $result, $match)) {
  456.  
  457.             // Removes the derivational suffix from given word
  458.             $result = preg_replace($derivational_suffix, '', $result);
  459.  
  460.             // Updates the removed value holder
  461.             $this->removed['derivational_suffix'] = $match[0];
  462.  
  463.             // Perform database lookup
  464.             $check = $this->lookup($result);
  465.  
  466.             // If a lemma is successfully found, return it.
  467.             if($check) return $check;
  468.  
  469.         }
  470.  
  471.         return $result;
  472.  
  473.     }
  474.  
  475.  
  476.     /*
  477.         Attempts to remove derivational prefixes di-, ke-, se-, be-, pe-,
  478.         me-, pe- from input word. Generally, derivational prefix is divided to
  479.         2 different group:
  480.             plain (di-, ke-, se-) and
  481.             complex (be-,me-,pe-,te-)
  482.  
  483.         Complex prefixes need transformation rules for certain cases in order to
  484.         correctly lemmatize the input word.
  485.  
  486.         @param string $word
  487.         @return mixed
  488.     */
  489.     protected function delete_derivational_prefix($word) {
  490.  
  491.         /*
  492.             Loads normalized vowel regex from class' [constant]; for shorthand purposes.
  493.  
  494.             @var string
  495.         */
  496.         $vowel = self::VOWEL;
  497.  
  498.         /*
  499.             Loads normalized consonant regex from class' [constant]; for shorthand purposes.
  500.  
  501.             @var string
  502.         */
  503.         $consonant = self::CONSONANT;
  504.  
  505.         /*
  506.             Loads normalized alphabet regex (including stripes) from class' [constant];
  507.             for shorthand purposes.
  508.  
  509.             @var string
  510.         */
  511.         $alpha = self::ALPHA;
  512.  
  513.         /*
  514.             Holds the value after suffix removal process
  515.  
  516.             @var string
  517.         */
  518.         $result = $word;
  519.  
  520.         /*
  521.             Records what type of prefix is removed; plain or complex,
  522.             in boolean form with [TRUE for plain]
  523.  
  524.             @var boolean
  525.         */
  526.         $type;
  527.  
  528.         /*
  529.             Records what the matching prefix is for later use
  530.  
  531.             @var string
  532.         */
  533.         $prefix;
  534.  
  535.         /*  
  536.             Regular expressions for plain and complex derivational prefixes
  537.  
  538.             @var array list of strings
  539.         */
  540.         $patterns = array(
  541.                 'plain' => "/^(di|(k|s)e)/",
  542.                 'complex' => "/^(b|m|p|t)e/"
  543.             );
  544.  
  545.         /*
  546.            
  547.             A check is performed; if the input word has less than four characters,
  548.             then the prefix removal process will be skipped.
  549.  
  550.         */
  551.         if(strlen($result)< 4) {
  552.             return $result;
  553.         }
  554.  
  555.  
  556.         foreach($patterns as $key => $pattern) {
  557.  
  558.             if(preg_match($pattern, $result, $match)) {
  559.  
  560.                 // saves the detected prefix's type
  561.                 $type = ($key=='plain') ? true : false;
  562.  
  563.                 // saves matching prefix for later usage
  564.                 $prefix = $match[0];
  565.  
  566.  
  567.                 /*
  568.                    
  569.                     Performs check whether identified prefix is identical with the
  570.                     previously removed prefixes; the prefix removal process will be
  571.                     terminated here if duplicate prefix detected.
  572.  
  573.                 */
  574.                 if($this->removed["derivational_prefix"]!="" && in_array($prefix, $this->removed["derivational_prefix"])) {
  575.  
  576.                     return $result;
  577.  
  578.                 }
  579.  
  580.  
  581.                 /*
  582.  
  583.                     Initializes recoding variable for found prefix; if the corresponding
  584.                     rule does not have recoding path, then the value will be empty string
  585.  
  586.                 */
  587.                 $this->recoding_tracker[$match[0]] = "";
  588.  
  589.                 /*
  590.  
  591.                     If the prefix belongs to the 'plain' group, then immediate removal is done;
  592.                     However if then prefix belongs to complex group, transformation rules must apply
  593.  
  594.                 */
  595.                 if($type) {
  596.  
  597.                     $array = $this->removed['derivational_prefix'];
  598.                    
  599.                     if($prefix=='ke' && $array!="" && ($array[0]=="di" && !preg_match('/(tawa|tahu)/', $result)) && $array[0]!="be") return $result;
  600.  
  601.                     $result = preg_replace($pattern, '', $result);
  602.  
  603.                     // save modification changes to prefix tracker
  604.                     $this->complex_prefix_tracker[$prefix] = array($prefix => "");
  605.  
  606.                 } else {
  607.  
  608.                     /*
  609.                         Temporary single-member array, used to hold complex prefix transformations
  610.                         to be pushed to the tracker.
  611.  
  612.                         @var array
  613.                     */
  614.                     $modification = null;
  615.  
  616.                     /*************************************************************************
  617.                     **  "be-" PREFIX RULES
  618.                     **   total rule: 5  
  619.                     *************************************************************************/
  620.  
  621.                     if($prefix == "be") {
  622.  
  623.                         /*
  624.                            
  625.                             If a prefix has been removed before, these rules check for
  626.                             combination, if it is an allowed type of combination or not.
  627.  
  628.                         */
  629.                         if($this->removed['derivational_prefix']!="") {
  630.  
  631.                             // Get the array value of first index
  632.                             $array = reset($this->complex_prefix_tracker);
  633.  
  634.                             // Get the first index of modification value
  635.                             $added = reset($array);
  636.  
  637.                             // pp: Previous Prefix; Get the key (removed part) of modification value
  638.                             $pp = key($array);
  639.                            
  640.                             /*
  641.  
  642.                                 Allowed combinations:
  643.                                 diber-,
  644.                                 keber-,
  645.                                 member-,
  646.                                 pember
  647.            
  648.                             */
  649.                             if($pp!='mem' && $pp!='pem' && $pp!= 'di' && $pp!='ke') return $result;
  650.  
  651.                         }
  652.  
  653.                         /*
  654.                             RULE 1
  655.                             input: berV...
  656.                             output: berV... | be - rV...
  657.                         */
  658.                         if(preg_match("/^ber$vowel/", $result)) {
  659.  
  660.                             $result = preg_replace("/^ber/", "", $result);
  661.  
  662.                             // save prefix changes
  663.                             $modification = array("ber" => "");
  664.  
  665.                             // save recoding path
  666.                             $this->recoding_tracker[$prefix] = array("be" => "");
  667.  
  668.                         }
  669.  
  670.                         /*
  671.                             RULE 2
  672.                             input: berCAP... where C!='r' and P!='er'
  673.                             output: ber-CAP...
  674.                         */
  675.                         else if(preg_match("/^ber[bcdfghjklmnpqstvwxyz][a-z](?!er)/", $result)) {
  676.  
  677.                             $result = preg_replace("/^ber/", "", $result);
  678.  
  679.                             // save prefix changes
  680.                             $modification = array("ber" => "");
  681.                         }
  682.  
  683.                         /*
  684.                             RULE 3
  685.                             input: berCAerV... where C!= 'r'
  686.                             output: ber-CAerV
  687.                         */
  688.                         else if(preg_match("/^ber[bcdfghjklmnpqstvwxyz][a-z]er$vowel/", $result)) {
  689.  
  690.                             $result = preg_replace("/^ber/", "", $result);
  691.  
  692.                             //save prefix changes
  693.                             $modification = array("ber" => "");
  694.  
  695.                         }
  696.  
  697.                         /*
  698.                             RULE 4
  699.                             input: belajar
  700.                             output: bel - ajar
  701.                         */
  702.                         else if(preg_match("/^belajar$/", $result)) {
  703.  
  704.                             $result = preg_replace("/^bel/", "", $result);
  705.  
  706.                             // save prefix changes
  707.                             $modification = array("bel" => "");
  708.  
  709.                         }
  710.  
  711.                         /*
  712.                             RULE 5
  713.                             input: beC1erC2... where C1!= 'r' or 'l'
  714.                             output: be-C1erC2
  715.                         */
  716.                         else if(preg_match("/^be[bcdfghjkmnpqstvwxyz]er$consonant/", $result)) {
  717.  
  718.                             $result = preg_replace("/^be/", "", $result);
  719.  
  720.                             // save prefix changes
  721.                             $modification = array("be" => "");
  722.  
  723.                         }
  724.  
  725.                         /*
  726.  
  727.                             In this case, the rule is unsuccessful, therefore the
  728.                             original input word will be returned. The previously
  729.                             initialized recoding chars will also be unset.
  730.  
  731.                         */
  732.                         else {
  733.  
  734.                             unset($this->recoding_tracker[$prefix]);
  735.  
  736.                             return $word;
  737.                         }
  738.  
  739.                     }
  740.                    
  741.  
  742.                     /*************************************************************************
  743.                     **  "te-" PREFIX RULES
  744.                     **  total rule: 5
  745.                     *************************************************************************/
  746.                    
  747.                     else if($prefix == "te") {
  748.  
  749.                         /*
  750.                            
  751.                             If a prefix has been removed before, these rules check for
  752.                             combination, if it is an allowed type of combination or not.
  753.  
  754.                         */
  755.                         if($this->removed['derivational_prefix']!="") {
  756.  
  757.                             // Get the array value of first index
  758.                             $array = reset($this->complex_prefix_tracker);
  759.  
  760.                             // Get the first index of modification value
  761.                             $added = reset($array);
  762.  
  763.                             // pp: Previous Prefix; Get the key (removed part) of modification value
  764.                             $pp = key($array);
  765.                            
  766.                             /*
  767.                            
  768.                                 Allowed combinations:
  769.                                 ke-,
  770.                                 men- (special for tawa),
  771.                                 pen- (special for tawa)
  772.            
  773.                             */
  774.                             if($pp!='ke' && (($pp=='me' || $pp=='men' || $pp=='pen') && !preg_match('/tawa/', $result))) {
  775.                                
  776.                                 return $result;
  777.                             }// menerbangkan
  778.                             //
  779.                         }
  780.  
  781.                         /*
  782.                             RULE 6
  783.                             input: terV...
  784.                             output: ter-V... | te-rV...
  785.                         */
  786.                         if(preg_match("/^ter$vowel/", $result)) {
  787.  
  788.                             $result = preg_replace("/^ter/", "", $result);
  789.  
  790.                             // save prefix changes
  791.                             $modification = array("ter" => "");
  792.  
  793.                             // save recoding path
  794.                             $this->recoding_tracker[$prefix] = array("te" => "");
  795.  
  796.                         }
  797.  
  798.                         /*
  799.                             RULE 7
  800.                             input: terCerV...
  801.                             output: ter-CerV... where C!='r'
  802.                         */
  803.                         else if(preg_match("/^ter[bcdfghjklmnpqstvwxyz]er$vowel/", $result)) {
  804.  
  805.                             $result = preg_replace("/^ter/", "", $result);
  806.  
  807.                             // save prefix changes
  808.                             $modification = array("ter" => "");
  809.  
  810.                         }
  811.  
  812.                         /*
  813.                             RULE 8
  814.                             input: terCP...
  815.                             output: ter-CP...
  816.                         */
  817.                         else if(preg_match("/^ter$consonant(?!er)/", $result)) {
  818.  
  819.                             $result = preg_replace("/^ter/", "", $result);
  820.  
  821.                             // save prefix changes
  822.                             $modification = array("ter" => "");
  823.  
  824.                         }
  825.  
  826.                         /*
  827.                             RULE 9
  828.                             input: teC1erC2...
  829.                             output: te-C1erC2... where C1!='r'
  830.                         */
  831.                         else if(preg_match("/^ter[bcdfghjklmnpqstvwxyz]er$consonant/", $result)) {
  832.  
  833.                             $result = preg_replace("/^te/", "", $result);
  834.  
  835.                             // save prefix changes
  836.                             $modification = array("te" => "");
  837.  
  838.                         }
  839.  
  840.                         /*
  841.                             RULE 10
  842.                             input: terC1erC2...
  843.                             output: ter-C1erC2... where C1!='r'
  844.                         */
  845.                         else if(preg_match("/^ter[bcdfghjklmnpqstvwxyz]er$consonant/", $result)) {
  846.  
  847.                             $result = preg_replace("/^ter/", "", $result);
  848.  
  849.                             // save prefix changes
  850.                             $modification = array("ter", "");
  851.  
  852.                         }
  853.  
  854.                         /*
  855.  
  856.                             In this case, the rule is unsuccessful, therefore the
  857.                             original input word will be returned. The previously
  858.                             initialized recoding chars will also be unset.
  859.  
  860.                         */
  861.                         else {
  862.  
  863.                             unset($this->recoding_tracker[$prefix]);
  864.                            
  865.                             return $word;
  866.                         }
  867.  
  868.                     }
  869.  
  870.  
  871.                     /*************************************************************************
  872.                     **  "me-" PREFIX RULES
  873.                     **  total rule: 10
  874.                     *************************************************************************/
  875.  
  876.                     else if($prefix == "me") {
  877.  
  878.                         /*
  879.                            
  880.                             This prefix cannot be a second-level prefix. If there is
  881.                             already a removed prefix, immediately return input word.
  882.  
  883.                         */
  884.                         if($this->removed['derivational_prefix']!="") return $result;
  885.  
  886.                         /*
  887.                             RULE 11
  888.                             input: me{l|r|w|y}V...
  889.                             output: me-{l|r|w|y}V...
  890.                         */
  891.                         if(preg_match("/^me[lrwy]$vowel/", $result)) {
  892.  
  893.                             $result = preg_replace("/^me/", "", $result);
  894.  
  895.                             // save prefix changes
  896.                             $modification = array("me" => "");
  897.  
  898.                         }
  899.  
  900.                         /*
  901.                             RULE 12
  902.                             input: mem{b|f|v}...
  903.                             output: mem-{b|f|v}...
  904.                         */
  905.                         else if(preg_match("/^mem[bfv]/", $result)) {
  906.  
  907.                             $result = preg_replace("/^mem/", "", $result);
  908.  
  909.                             // save prefix changes
  910.                             $modification = array("mem" => "");
  911.  
  912.                         }
  913.  
  914.                         /*
  915.                             RULE 13
  916.                             input: mempe...
  917.                             output: mem-pe..
  918.                         */
  919.                         else if(preg_match("/^mempe/", $result)) {
  920.  
  921.                             $result = preg_replace("/^mem/", "", $result);
  922.  
  923.                             // save prefix changes
  924.                             $modification = array("mem" => "");
  925.  
  926.                         }
  927.  
  928.                         /*
  929.                             RULE 14
  930.                             input: mem{rV|V}...
  931.                             output:me-m{rV|V}... | me-p{rV|V}...
  932.                         */
  933.                         else if(preg_match("/^mem(r?)$vowel/", $result, $match)) {
  934.  
  935.                             $result = preg_replace("/^me/", "", $result);
  936.  
  937.                             // save prefix changes
  938.                             $modification = array("me$match[1]" => "");
  939.  
  940.                             // save recoding path
  941.                             $this->recoding_tracker[$prefix] = array("mem" => "p");
  942.  
  943.                         }
  944.  
  945.                         /*
  946.                             RULE 15
  947.                             input: men{c|d|j|s|z}...
  948.                             output:men-{c|dj|s|z}...
  949.                         */
  950.                         else if(preg_match("/^men[cdsjz]/", $result)) {
  951.  
  952.                             $result = preg_replace("/^men/", "", $result);
  953.  
  954.                             // save prefix changes
  955.                             $modification = array("men" => "");
  956.  
  957.                         }
  958.  
  959.                         /*
  960.                             RULE 16
  961.                             input: menV...
  962.                             output:me-tV... | me-nV...
  963.                         */
  964.                         else if(preg_match("/^men$vowel/", $result)) {
  965.  
  966.                             $result = preg_replace("/^men/", "t", $result);
  967.  
  968.                             // save prefix changes
  969.                             $modification = array("men" => "t");
  970.  
  971.                             // save recoding path
  972.                             $this->recoding_tracker[$prefix] = array("me" => "");
  973.  
  974.                         }
  975.  
  976.                         /*
  977.                             RULE 17
  978.                             input: meng{g|h|q|k}...
  979.                             output: meng-{g|h|q|k}...
  980.                         */
  981.                         else if(preg_match("/^meng[ghqk]/", $result)) {
  982.  
  983.                             $result = preg_replace("/^meng/", "", $result);
  984.  
  985.                             // save prefix changes
  986.                             $modification = array("meng" => "");
  987.  
  988.                         }
  989.  
  990.                         /*
  991.                             RULE 18
  992.                             input: mengV...
  993.                             output: meng-V... | meng-kV... | mengV-... if V='e'
  994.                         */
  995.                         else if(preg_match("/^meng($vowel)/", $result, $match)) {
  996.  
  997.                             $result = preg_replace("/^meng/", "", $result);
  998.  
  999.                             // save prefix changes
  1000.                             $modification = array("meng" => "");
  1001.  
  1002.                             // save recoding path
  1003.                             $this->recoding_tracker[$prefix] = array("meng1" => "k");
  1004.                             $this->recoding_tracker[$prefix]["menge"] = "";
  1005.                            
  1006.                         }
  1007.  
  1008.                         /*
  1009.                             RULE 19
  1010.                             input: menyV...
  1011.                             output: meny-sV... | me-nyV...
  1012.                         */
  1013.                         else if(preg_match("/^meny$vowel/", $result)) {
  1014.  
  1015.                             $result = preg_replace("/^me/", "", $result);
  1016.  
  1017.                             // save prefix changes
  1018.                             $modification = array("me" => "");
  1019.  
  1020.                             // save recoding path
  1021.                             $this->recoding_tracker[$prefix] = array("meny" => "s");
  1022.  
  1023.                         }
  1024.  
  1025.                         /*
  1026.                             RULE 20
  1027.                             input: mempA...
  1028.                             output: mem-pA... where A!='e'
  1029.                         */
  1030.                         else if(preg_match("/^memp[abcdfghijklmnopqrstuvwxyz]/", $result)) {
  1031.  
  1032.                             $result = preg_replace("/^mem/", "", $result);
  1033.  
  1034.                             // save prefix changes
  1035.                             $modification = array("mem" => "");
  1036.  
  1037.                         }
  1038.  
  1039.                         /*
  1040.  
  1041.                             In this case, the rule is unsuccessful, therefore the
  1042.                             original input word will be returned. The previously
  1043.                             initialized recoding chars will also be unset.
  1044.  
  1045.                         */
  1046.                         else {
  1047.  
  1048.                             unset($this->recoding_tracker[$prefix]);
  1049.                            
  1050.                             return $word;
  1051.                         }
  1052.  
  1053.                     }
  1054.  
  1055.  
  1056.                     /*************************************************************************
  1057.                     **  "pe-" PREFIX RULES
  1058.                     **  total rule: 15
  1059.                     *************************************************************************/
  1060.  
  1061.                     else if($prefix == "pe") {
  1062.  
  1063.                         /*
  1064.                            
  1065.                             If a prefix has been removed before, these rules check for
  1066.                             combination, if it is an allowed type of combination or not.
  1067.  
  1068.                         */
  1069.                         if($this->removed['derivational_prefix']!="") {
  1070.  
  1071.                             // Get the array value of first index
  1072.                             $array = reset($this->complex_prefix_tracker);
  1073.  
  1074.                             // Get the first index of modification value
  1075.                             $added = reset($array);
  1076.  
  1077.                             // pp: Previous Prefix; Get the key (removed part) of modification value
  1078.                             $pp = key($array);
  1079.                            
  1080.                             /*
  1081.  
  1082.                                 Allowed combinations:
  1083.                                 di-,
  1084.                                 peN-,
  1085.                                 mem-.
  1086.            
  1087.                             */
  1088.                             if($pp!='di' && $pp!='ber' && $pp!= 'mem' && $pp!='se' && $pp!='ke') return $result;
  1089.  
  1090.                         }
  1091.  
  1092.                         /*
  1093.                             RULE 21
  1094.                             input: pe{w|y}V...
  1095.                             output: pe-{w|y}V...
  1096.                         */
  1097.                         if(preg_match("/^pe[wy]$vowel/", $result)) {
  1098.  
  1099.                             $result = preg_replace("/^pe/", "", $result);
  1100.  
  1101.                             // save prefix changes
  1102.                             $modification = array("pe" => "");
  1103.  
  1104.                         }
  1105.  
  1106.                         /*
  1107.                             RULE 22
  1108.                             input: perV...
  1109.                             output: per-V... | pe-rV...
  1110.                         */
  1111.                         else if(preg_match("/^per$vowel/", $result)) {
  1112.  
  1113.                             $result = preg_replace("/^per/", "", $result);
  1114.  
  1115.                             // save prefix changes
  1116.                             $modification = array("per" => "");
  1117.  
  1118.                             // save recoding path
  1119.                             $this->recoding_tracker[$prefix] = array("pe" => "");
  1120.  
  1121.                         }
  1122.  
  1123.                         /*
  1124.                             RULE 23
  1125.                             input: perCAP...
  1126.                             output: per-CAP... where C!='r' and P!='er'
  1127.                         */
  1128.                         else if(preg_match("/^per[bcdfghjklmnpqstvwxyz][a-z](?!er)/", $result)) {
  1129.  
  1130.                             $result = preg_replace("/^per/", "", $result);
  1131.  
  1132.                             // save prefix changes
  1133.                             $modification = array("per" => "");
  1134.  
  1135.                         }
  1136.  
  1137.                         /*
  1138.                             RULE 24
  1139.                             input: perCAerV...
  1140.                             output: per-CAerV... where C!= 'r'
  1141.                         */
  1142.                         else if(preg_match("/^per[bcdfghjklmnpqstvwxyz][a-z]er$vowel/", $result)) {
  1143.  
  1144.                             $result = preg_replace("/^per/", "", $result);
  1145.  
  1146.                             // save prefix changes
  1147.                             $modification = array("per" => "");
  1148.  
  1149.                         }
  1150.  
  1151.                         /*
  1152.                             RULE 25
  1153.                             input: pem{b|f|v}...
  1154.                             output: pem-{b|f|v}...
  1155.                         */
  1156.                         else if(preg_match("/^pem[bfv]/", $result)) {
  1157.  
  1158.                             $result = preg_replace("/^pem/", "", $result);
  1159.  
  1160.                             // save prefix changes
  1161.                             $modification = array("pem" => "");
  1162.  
  1163.                         }
  1164.  
  1165.                         /*
  1166.                             RULE 26
  1167.                             input: pem{rV|V}...
  1168.                             output: pe-m{rV|V}... | pe-p{rV|V}...
  1169.                         */
  1170.                         else if(preg_match("/^pem(r?)$vowel/", $result)) {
  1171.  
  1172.                             $result = preg_replace("/^pe/", "", $result);
  1173.  
  1174.                             // save prefix changes
  1175.                             $modification = array("pe" => "");
  1176.  
  1177.                             // save recoding path
  1178.                             $this->recoding_tracker[$prefix] = array("pem" => "p");
  1179.                      
  1180.                         }
  1181.  
  1182.                         /*
  1183.                             RULE 27
  1184.                             input: pen{c|d|j|z}...
  1185.                             output: pen-{c|d|j|z}...
  1186.                         */
  1187.                         else if(preg_match("/^pen[cdjz]/", $result)) {
  1188.  
  1189.                             $result = preg_replace("/^pen/", "", $result);
  1190.  
  1191.                             // save prefix changes
  1192.                             $modification = array("pen" => "");
  1193.  
  1194.                         }
  1195.  
  1196.                         /*
  1197.                             RULE 28
  1198.                             input: penV...
  1199.                             output: pe-tV... | pe-nV...
  1200.                         */
  1201.                         else if(preg_match("/^pen$vowel/", $result)) {
  1202.  
  1203.                             $result = preg_replace("/^pen/", "t", $result);
  1204.  
  1205.                             // save prefix changes
  1206.                             $modification = array("pen" => "t");
  1207.  
  1208.                             // save recoding path
  1209.                             $this->recoding_tracker[$prefix] = array("pe" => "");
  1210.  
  1211.                         }
  1212.  
  1213.                         /*
  1214.                             RULE 29
  1215.                             input: pengC...
  1216.                             output: peng-C...
  1217.                         */
  1218.                         else if(preg_match("/^peng$consonant/", $result)) {
  1219.  
  1220.                             $result = preg_replace("/^peng/", "", $result);
  1221.  
  1222.                             // save prefix changes
  1223.                             $modification = array("peng" => "");
  1224.  
  1225.                         }
  1226.  
  1227.                         /*
  1228.                             RULE 30
  1229.                             input: pengV...
  1230.                             output: peng-V | peng-kV... | pengV-... if V='e'
  1231.                         */
  1232.                         else if(preg_match("/^peng($vowel)/", $result, $match)) {
  1233.  
  1234.                             $result = preg_replace("/^peng/", "", $result);
  1235.  
  1236.                             // save prefix changes
  1237.                             $modification = array("peng" => "");
  1238.  
  1239.                             // save recoding path
  1240.                             $this->recoding_tracker[$prefix] = array("peng1" => "k");
  1241.                             $this->recoding_tracker[$prefix]["penge"] = "";
  1242.  
  1243.  
  1244.                             // if($match[1] == 'e') {
  1245.  
  1246.                             //     $result = preg_replace("/^penge/", "", $result);
  1247.  
  1248.                             //     // save prefix changes
  1249.                             //     $modification = array("penge" => "");
  1250.  
  1251.                             //     $this->recoding_tracker[$prefix] = array("peng1" => "");
  1252.                             //     $this->recoding_tracker[$prefix]["peng2"] = "k";
  1253.  
  1254.                             // } else {
  1255.  
  1256.                             //     $result = preg_replace("/^peng/", "", $result);
  1257.  
  1258.                             //     // save prefix changes
  1259.                             //     $modification = array("peng" => "");
  1260.  
  1261.                             //     // save recoding path
  1262.                             //     $this->recoding_tracker[$prefix] = array("peng" => "k");    
  1263.                             // }
  1264.  
  1265.                         }
  1266.  
  1267.                         /*
  1268.                             RULE 31
  1269.                             input: penyV...
  1270.                             output: peny-sV... | pe-nyV...
  1271.                         */
  1272.                         else if(preg_match("/^peny$vowel/", $result)) {
  1273.  
  1274.                             $result = preg_replace("/^peny/", "s", $result);
  1275.  
  1276.                             // save prefix changes
  1277.                             $modification = array("peny" => "s");
  1278.  
  1279.                             // save recoding path
  1280.                             $this->recoding_tracker[$prefix] = array("pe" => "");
  1281.  
  1282.                         }
  1283.  
  1284.                         /*
  1285.                             RULE 32
  1286.                             input: pelV...
  1287.                             output: pe-lV... | pel-V if 'pelajar'
  1288.                         */
  1289.                         else if(preg_match("/^pel$vowel/", $result)) {
  1290.  
  1291.                             if($result == "pelajar") {
  1292.  
  1293.                                 $result = preg_replace("/^pel/", "", $result);
  1294.  
  1295.                                 // save prefix changes
  1296.                                 $modification = array("pel" => "");
  1297.  
  1298.                             } else {
  1299.  
  1300.                                 $result = preg_replace("/^pe/", "", $result);
  1301.  
  1302.                                 // save prefix changes
  1303.                                 $modification = array("pe" => "");
  1304.  
  1305.                             }
  1306.  
  1307.                         }
  1308.  
  1309.                         /*
  1310.                             RULE 33
  1311.                             input: peCerV...
  1312.                             output: per-CerV... where C!={r|w|y|l|m|n}
  1313.                         */
  1314.                         else if(preg_match("/^pe[bcdfghjkpqstvxz]er$vowel/", $result)) {
  1315.  
  1316.                             $result = preg_replace("/^pe/", "", $result);
  1317.  
  1318.                             // save prefix changes
  1319.                             $modification = array("pe" => "");
  1320.  
  1321.                         }
  1322.  
  1323.                         /*
  1324.                             RULE 34
  1325.                             input: peCP...
  1326.                             output: pe-CP... where C!={r|w|y|l|m|n} and P!='er'
  1327.                         */
  1328.                         else if(preg_match("/^pe[bcdfghjkpqstvxz](?!er)/", $result)) {
  1329.  
  1330.                             $result = preg_replace("/^pe/", "", $result);
  1331.  
  1332.                             // save prefix changes
  1333.                             $modification = array("pe" => "");
  1334.  
  1335.                         }
  1336.  
  1337.                         /*
  1338.                             RULE 35
  1339.                             input: peC1erC2...
  1340.                             output: pe-C1erC2... where C1!={r|w|y|l|m|n}
  1341.                         */
  1342.                         else if(preg_match("/^pe[bcdfghjkpqstvxz]er$consonant/", $result)) {
  1343.  
  1344.                             $result = preg_replace("/^pe/", "", $result);
  1345.  
  1346.                             // save prefix changes
  1347.                             $modification = array("pe", "");
  1348.  
  1349.                         }
  1350.  
  1351.                         /*
  1352.  
  1353.                             In this case, the rule is unsuccessful, therefore the
  1354.                             original input word will be returned. The previously
  1355.                             initialized recoding chars will also be unset.
  1356.  
  1357.                         */
  1358.                         else {
  1359.  
  1360.                             unset($this->recoding_tracker[$prefix]);
  1361.                            
  1362.                             return $word;
  1363.                         }
  1364.  
  1365.  
  1366.                     }
  1367.  
  1368.                     /*
  1369.                        
  1370.                         Moves the temporary saved modification to prefix tracker
  1371.                         attribute (provided it's not null); If there is no modification
  1372.                         detected, then the this process is terminated.
  1373.  
  1374.                     */
  1375.                     if($modification!=null) {
  1376.  
  1377.                         // saves modification changes to prefix tracker
  1378.                         $this->complex_prefix_tracker[$prefix] = $modification;
  1379.  
  1380.                     } else {
  1381.  
  1382.                         // If there is no changes made, return original word.
  1383.                         return $result;
  1384.  
  1385.                     }
  1386.                    
  1387.                 }
  1388.  
  1389.                 /*
  1390.                    
  1391.                     Updates the removed value holder. Since derivational prefix
  1392.                     is stackable (up to 2), the value is kept in an array fashion
  1393.  
  1394.                 */
  1395.                 if($this->removed['derivational_prefix']=='') {
  1396.  
  1397.                     $this->removed['derivational_prefix'] = array();
  1398.  
  1399.                 }
  1400.  
  1401.                 // Adds the detected prefix type to the removed affix tracker.
  1402.                 array_push($this->removed['derivational_prefix'], $prefix);
  1403.  
  1404.                 // Performs dictionary lookup
  1405.                 $this->lookup($result);
  1406.  
  1407.                 // once the prefix is removed, we need to enter next iteration.
  1408.                 return $result;
  1409.  
  1410.             }
  1411.  
  1412.         }
  1413.  
  1414.         // if no prefix found, return original word instead
  1415.         return $result;
  1416.  
  1417.     }
  1418.  
  1419.     /*
  1420.         Performs recoding on input word
  1421.         (provided there are recoding paths available)
  1422.  
  1423.         @param string $word
  1424.         @return mixed
  1425.     */
  1426.     protected function recode($word) {
  1427.  
  1428.         /*
  1429.             Holds the value after suffix removal process
  1430.  
  1431.             @var string
  1432.         */
  1433.         $result = $word;      
  1434.  
  1435.         /*
  1436.             Holds the reversed version of prefix tracker; because it is used
  1437.             to return previously removed prefixes.
  1438.            
  1439.             @var array
  1440.         */
  1441.         $prefixes = array_reverse($this->complex_prefix_tracker);
  1442.  
  1443.  
  1444.         /*
  1445.            
  1446.             For each iteration, check whether the prefix has recoding path(s).
  1447.             If recoding path is found, then it will be applied
  1448.  
  1449.         */
  1450.         foreach($prefixes as $prefix => $changes) {
  1451.  
  1452.             /*
  1453.  
  1454.                 Checks whether the current prefix has available recoding path,
  1455.                 stored in a variable
  1456.  
  1457.                 @var array
  1458.             */
  1459.             $recode = $this->recoding_tracker[$prefix];
  1460.  
  1461.             /*
  1462.                 fetch the added value when removing this prefix
  1463.  
  1464.                 @var string
  1465.             */
  1466.             $prefix_added = reset($changes);
  1467.  
  1468.             /*
  1469.                 fetch the removed value when removing this prefix
  1470.  
  1471.                 @var string
  1472.             */
  1473.             $prefix_removed = key($changes);
  1474.  
  1475.             /*
  1476.  
  1477.                 If something was added in the process of current prefix's removal,
  1478.                 then it will be removed; and replaced with the removed value.
  1479.  
  1480.             */
  1481.             if($prefix_added!="") {
  1482.  
  1483.                 // replace the added value with the removed value
  1484.                 $result = preg_replace("/^$prefix_added/", $prefix_removed, $result);
  1485.  
  1486.             }
  1487.             else {
  1488.  
  1489.                 // prepend the removed value to current word
  1490.                 $result = $prefix_removed . $result;
  1491.             }          
  1492.  
  1493.             /*
  1494.  
  1495.                 If a recoding path is available, then it will be checked whether
  1496.                 there are more than one path. For every path, the word is configured
  1497.                 with the recoding path, and checked against the database.
  1498.  
  1499.             */
  1500.             if($recode!="") {
  1501.  
  1502.                 /*
  1503.                     Temporary variable for storing word changes; used for checking
  1504.                     and lookup
  1505.  
  1506.                     @var string
  1507.                 */
  1508.                 $temp;
  1509.  
  1510.                 foreach($recode as $raw_removed => $added) {
  1511.  
  1512.                     /*
  1513.                         There are some cases where the recoding path is more than
  1514.                         one, and both have identical removed value; because this
  1515.                         can cause duplicate array keys (which will lead to overwriting),
  1516.                         some rules are appended with numbers. Before the removed value
  1517.                         is stored, it removes any number appended in the value
  1518.  
  1519.                         @var string
  1520.                     */
  1521.                     $removed = preg_replace("/[0-9]+/", "", $raw_removed);
  1522.  
  1523.                     // Attempts to apply recoding path.
  1524.                     $temp = preg_replace("/^$removed/", ($added) ? $added : "", $result);
  1525.  
  1526.                     /*
  1527.                        
  1528.                         Performs dictionary lookup. If found, this will return the lookup result,
  1529.                         and updates class' property: $found
  1530.  
  1531.                     */
  1532.                     if($this->lookup($temp)) {
  1533.                        
  1534.                         // updates the prefix tracker value
  1535.                         $this->complex_prefix_tracker[$prefix] = array($removed => $added);
  1536.  
  1537.                         // returns the result
  1538.                         return $temp;
  1539.                    
  1540.                     }
  1541.  
  1542.                     $previous = "";
  1543.  
  1544.                     // records to variable to $record for continued processing
  1545.                     $record = $temp;
  1546.  
  1547.                     $before = count($this->complex_prefix_tracker);
  1548.  
  1549.                     // the iteration is done for maximum three times
  1550.                     for($i=0; $i<3; $i++) {
  1551.  
  1552.                         /*
  1553.                             Temporary variable; holds the value before the word
  1554.                             undergoes derivation prefix removal. Used for comparison,
  1555.                             whether
  1556.  
  1557.                             @var string
  1558.                         */
  1559.                         $previous = $record;
  1560.  
  1561.                         // delete derivational prefix
  1562.                         $record = $this->delete_derivational_prefix($record);
  1563.  
  1564.                         /*
  1565.  
  1566.                             Checks for disallowed affix combination,
  1567.                             Checks if the lemma is already found,
  1568.                             Checks if the no prefix was removed, or the amount of prefixes removed are already 2.
  1569.  
  1570.                         */
  1571.                         if(($i==0 && $this->has_disallowed_pairs())
  1572.                             || $record == $previous
  1573.                             || count($this->removed['derivational_prefix'])>3)
  1574.                         {
  1575.                             break;
  1576.                         }
  1577.                         else if($this->found) return $record;
  1578.                     }
  1579.  
  1580.                     if(count($this->complex_prefix_tracker) > $before) {
  1581.  
  1582.                         $count = 0;
  1583.                         foreach($this->complex_prefix_tracker as $key => $value) {
  1584.                             $count++;
  1585.                             if($count <= $before) continue;
  1586.  
  1587.                             unset($this->complex_prefix_tracker[$key]);
  1588.                             unset($this->removed['derivational_prefix'][$count-1]);
  1589.                         }
  1590.                     }
  1591.  
  1592.                 }
  1593.  
  1594.                 // updates result variable for next iteration
  1595.                 $result = $temp;
  1596.  
  1597.             }
  1598.  
  1599.         }
  1600.  
  1601.         // If recoding is unsuccessful or does not exist, return initial word
  1602.         return $word;
  1603.  
  1604.     }
  1605.  
  1606.  
  1607.     /**
  1608.    
  1609.         @todo   LOW - description will be available later! (once most of the things are up.)
  1610.                 name is still a jest, of course. we'll come up with something better!
  1611.    
  1612.         @todo   LOW - implementation documentation for BACKTRACKING procedure (case 7)
  1613.  
  1614.     */
  1615.     public function eat($word, $backtrack_step = false) {
  1616.  
  1617.         /*
  1618.             Serves as the container for prefix/suffix removal results
  1619.            
  1620.             @var string
  1621.         */
  1622.         $result = $word;
  1623.  
  1624.         /*
  1625.             Serves as the temporary variable; holds string if process works
  1626.             without error and holds FALSE if there is an detected error.
  1627.  
  1628.             @var mixed
  1629.         */
  1630.         $temp = $this->lookup($word);
  1631.  
  1632.         /*
  1633.            
  1634.             STEP 1: perform dictionary lookup on input word
  1635.  
  1636.         */
  1637.         if($temp) {
  1638.  
  1639.             return $temp;
  1640.  
  1641.         } else {
  1642.  
  1643.             /*
  1644.                 Checks the rule precedence; contains TRUE if derivational prefix
  1645.                 is performed first and false for otherwise
  1646.  
  1647.                 @var mixed
  1648.             */
  1649.             $steps = $this->check_rule_precedence($word);
  1650.  
  1651.             /*
  1652.  
  1653.                 STEP 2: function ordering based on rule precedence result
  1654.                 identifies whether this is a backtrack step or not;
  1655.                 if this is main step then perform rule precedence check
  1656.  
  1657.             */
  1658.             if($backtrack_step) {
  1659.  
  1660.                 $steps = array(5,6);
  1661.  
  1662.             } else {
  1663.  
  1664.                 if($steps) {
  1665.  
  1666.                     $steps = array(5,6,3,4,7);
  1667.  
  1668.                 } else {
  1669.  
  1670.                     $steps = array(3,4,5,6,7);
  1671.  
  1672.                 }
  1673.  
  1674.             }
  1675.  
  1676.             foreach($steps as $step) {
  1677.  
  1678.                 switch($step) {
  1679.  
  1680.                     // STEP 3: delete inflectional suffix
  1681.                     case 3:
  1682.                         $temp = $this->delete_inflectional_suffix($result);
  1683.                         break;
  1684.  
  1685.                     // STEP 4: delete derivational suffix
  1686.                     case 4:
  1687.                         $temp = $this->delete_derivational_suffix($result);
  1688.                         break;
  1689.  
  1690.                     // STEP 5: delete derivational prefix
  1691.                     case 5:
  1692.                         // records to variable to $temp for continued processing
  1693.                         $temp = $result;
  1694.  
  1695.                         // the iteration is done for maximum three times
  1696.                         for($i=0; $i<3; $i++) {
  1697.  
  1698.                             /*
  1699.                                 Temporary variable; holds the value before the word
  1700.                                 undergoes derivation prefix removal. Used for comparison,
  1701.                                 whether
  1702.  
  1703.                                 @var string
  1704.                             */
  1705.                             $previous = $temp;
  1706.  
  1707.                             // delete derivational prefix
  1708.                             $temp = $this->delete_derivational_prefix($temp);
  1709.  
  1710.                             /*
  1711.  
  1712.                                 Checks for disallowed affix combination,
  1713.                                 Checks if the lemma is already found,
  1714.                                 Checks if the no prefix was removed, or the amount of prefixes removed are already 2.
  1715.  
  1716.                             */
  1717.                             if(($i==0 && $this->has_disallowed_pairs())
  1718.                                 || $this->found
  1719.                                 || $temp == $previous
  1720.                                 || count($this->removed['derivational_prefix'])>3)
  1721.                             {
  1722.                                 break;
  1723.                             }
  1724.                         }
  1725.                         break;
  1726.  
  1727.                     // STEP 6: perform recoding
  1728.                     case 6:
  1729.                         $temp = $this->recode($result);
  1730.                         break;
  1731.  
  1732.                     /**
  1733.                        
  1734.                         @todo implementation docs for backtracking
  1735.  
  1736.                     */
  1737.                     // STEP 7: perform suffix backtracking
  1738.                     case 7:
  1739.  
  1740.                         $prefixes = array_reverse($this->complex_prefix_tracker);
  1741.  
  1742.                         foreach($prefixes as $prefix => $changes) {
  1743.  
  1744.                             $prefix_added = reset($changes);
  1745.                             $prefix_removed = key($changes);
  1746.  
  1747.                             if($prefix_added!="") {
  1748.  
  1749.                                 $temp = preg_replace("/^$prefix_added/", $prefix_removed, $temp);
  1750.  
  1751.                             }
  1752.                             else {
  1753.  
  1754.                                 $temp = $prefix_removed . $temp;
  1755.                             }
  1756.                         }
  1757.  
  1758.                         $this->removed["derivational_prefix"] = "";
  1759.                         $this->complex_prefix_tracker = array();
  1760.                         $backtrack = $this->eat($temp, true);
  1761.  
  1762.                         if($this->found) break;
  1763.  
  1764.                         // return derivational suffix
  1765.                         if(!$this->found && $this->removed['derivational_suffix']!="") {
  1766.  
  1767.                             if($this->removed['derivational_suffix'] == "kan") {
  1768.  
  1769.                                 $temp = $temp . "k";
  1770.                                 $this->removed["derivational_prefix"] = "";
  1771.                                 $this->complex_prefix_tracker = array();
  1772.                                 $backtrack = $this->eat($temp, true);
  1773.  
  1774.                                 if($this->found) break;
  1775.  
  1776.                                 $temp = $temp . "an";
  1777.  
  1778.                             }
  1779.                             else {
  1780.  
  1781.                                 $temp = $temp . $this->removed["derivational_suffix"];
  1782.                                
  1783.                             }
  1784.                            
  1785.                             $this->removed["derivational_prefix"] = "";
  1786.                             $this->complex_prefix_tracker = array();
  1787.                             $backtrack = $this->eat($temp, true);
  1788.  
  1789.                         }
  1790.  
  1791.                         // return possessive pronoun
  1792.                         if(!$this->found && $this->removed["possessive_pronoun"]!="") {
  1793.  
  1794.                             $temp = $temp . $this->removed["possessive_pronoun"];
  1795.                             $this->removed["derivational_prefix"] = "";
  1796.                             $this->complex_prefix_tracker = array();
  1797.                             $backtrack = $this->eat($temp, true);
  1798.                            
  1799.                             if($this->found) break;
  1800.  
  1801.                         }
  1802.  
  1803.                         // return particle
  1804.                         if(!$this->found && $this->removed["particle"]!="") {
  1805.  
  1806.                             $temp = $temp . $this->removed["particle"];
  1807.                             $this->removed["derivational_prefix"] = "";
  1808.                             $this->complex_prefix_tracker = array();
  1809.                             $backtrack = $this->eat($temp, true);
  1810.                            
  1811.                             if($this->found) break;
  1812.  
  1813.                         }
  1814.  
  1815.                 }
  1816.  
  1817.                 /*
  1818.  
  1819.                      If the lookup already succeeded from previous result,
  1820.                      then directly return the result
  1821.  
  1822.                 */
  1823.                 if($this->found) return $this->found;
  1824.  
  1825.                 // if the removal is success, proceed to next step
  1826.                 $result = $temp;
  1827.  
  1828.             }
  1829.  
  1830.             /*
  1831.                
  1832.                 STEP 8: if the dictionary lookup still fails, return original word.
  1833.                 since the word was returned to its original form, removal histories
  1834.                 are considered 'undone'; for better semantics
  1835.  
  1836.             */
  1837.             if(!$backtrack_step) if(!$this->error) $this->error = "lemma_not_found";
  1838.             return $word;
  1839.  
  1840.         }
  1841.  
  1842.     }
  1843.  
  1844.  
  1845.     /**
  1846.  
  1847.         @debug  basically this gives access to the world about what prefixes/suffixes
  1848.                 have been removed. Will be removed later!
  1849.  
  1850.     **/
  1851.     public function getRemoved() {
  1852.  
  1853.         return $this->removed;
  1854.  
  1855.     }
  1856.  
  1857.     /*
  1858.  
  1859.         Closes database connection on instance destruction.
  1860.    
  1861.     */
  1862.     public function __destruct() {
  1863.  
  1864.         $this->database = null;
  1865.  
  1866.     }
  1867. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement