Created
August 3, 2018 21:06
-
-
Save StuffieStephie/aa50f12f1b0cebbb93fb02f579e47f90 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/*-------------------------------------------------------------- | |
# Dependancies | |
--------------------------------------------------------------*/ | |
if (!function_exists('mb_strlen')) { | |
function mb_strlen($input) { | |
if (preg_match('!!u', $input)) { | |
// this is utf-8 | |
return strlen( utf8_decode($input)); | |
} else { | |
// not utf-8 and hopefully a single byte | |
// where this solution falls through really | |
return strlen($input); | |
} | |
} | |
} | |
if (!function_exists('mb_substr')) { | |
function mb_substr($string, $offset, $length = null) { | |
$arr = preg_split("//u", $string); | |
$slice = array_slice($arr, $offset + 1, $length); | |
return implode("", $slice); | |
} | |
} | |
if (!function_exists('mb_strpos')) { | |
function mb_strpos($haystack, $needle, $offset = 0) { | |
return strpos($haystack, $needle, $offset); | |
} | |
} | |
if (!function_exists('mb_strtoupper')) { | |
function mb_strtoupper($input) { | |
return strtoupper($input); | |
} | |
} | |
if (!function_exists('mb_strtolower')) { | |
function mb_strtolower($input) { | |
return strtolower($input); | |
} | |
} | |
/*-------------------------------------------------------------- | |
# Actual Class | |
--------------------------------------------------------------*/ | |
/** | |
* Split a full name into its constituent parts | |
* - prefix/salutation (Mr. Mrs. Dr. etc) | |
* - given/first name | |
* - middle name/initial(s) | |
* - surname (last name) | |
* - surname base (last name without compounds) | |
* - surname compounds (only the compounds) | |
* - suffix (II, PhD, Jr. etc) | |
* | |
* Author: Josh Fraser | |
* | |
* Contribution from Clive Verrall www.cliveverrall.com February 2016 | |
* | |
* // other contributions: | |
* // - eric willis [list of honorifics] | |
* (http://notes.ericwillis.com/2009/11/common-name-prefixes-titles-and-honorifics/) | |
* // - `TomThak` for raising issue #16 and providing | |
* [wikipedia resource](https://cs.wikipedia.org/wiki/Akademick%C3%BD_titul) | |
* // - `atla5` for closing the issue. | |
*/ | |
class FullNameParser { | |
/** | |
* Create the dictionary of terms for use later | |
* | |
* - Common honorific prefixes (english) | |
* - Common compound surname identifiers | |
* - Common suffixes (lineage and professional) | |
* Note: Longer professional titles should appear earlier in the array than | |
* shorter titles to reduce the risk of mis-identification. | |
* (e.g. `BEng` before `BE`) | |
* Also note that case and periods are part of the matching for professional | |
* titles and therefore need to be correct, there are no case conversions. | |
*/ | |
protected $dict = array( | |
'prefix' => array( | |
'Mr.' => array('mr', 'mister', 'master'), | |
'Mrs.' => array('mrs', 'missus', 'missis'), | |
'Ms.' => array('ms', 'miss'), | |
'Dr.' => array('dr'), | |
'Rev.' => array("rev", "rev'd", "reverend"), | |
'Fr.' => array('fr', 'father'), | |
'Sr.' => array('sr', 'sister'), | |
'Prof.' => array('prof', 'professor'), | |
'Sir' => array('sir'), | |
'Hon.' => array('honorable'), | |
'Pres.' => array('president'), | |
'Gov' => array('governor','governer'), | |
'Ofc' => array('officer'), | |
'Msgr' => array('monsignor'), | |
'Sr.' => array('sister'), | |
'Br.' => array('brother'), | |
'Supt.' => array('superintendent'), | |
'Rep.' => array('representatitve'), | |
'Sen.' => array('senator'), | |
'Amb.' => array('ambassador'), | |
'Treas.' => array('treasurer'), | |
'Sec.' => array('secretary'), | |
'Pvt.' => array('private'), | |
'Cpl.' => array('corporal'), | |
'Sgt.' => array('sargent'), | |
'Adm.' => array('administrative','administrator','administrater'), | |
'Maj.' => array('major'), | |
'Capt.' => array('captain'), | |
'Cmdr.' => array('commander'), | |
'Lt.' => array('lieutenant'), | |
'Lt. Col.' => array('lieutenant colonel'), | |
'Col.' => array('colonel'), | |
'Gen.' => array('general'), | |
'Bc.' => array('bachelor', 'baccalaureus'), | |
'BcA.' => array('bachelor of arts', 'baccalaureus artis'), | |
'ICDr.' => array('doctor of canon law', 'juris cononici doctor'), | |
'Ing.' => array('engineer', 'ingenieur'), | |
'Ing. sheet.' => array('architect engineer', 'intrudes upon architectus'), | |
'JUDr.' => array('juris doctor utriusque', 'doctor rights'), | |
'MDDr.' => array('doctor of dental medicine', 'medicinae doctor dentium'), | |
'MgA.' => array('master of arts','magister artis'), | |
'Mgr.' => array('master'), | |
'MD.' => array('doctor of general medicine'), | |
'DVM.' => array('doctor of veterinary medine'), | |
'PaedDr.' => array('doctor of education'), | |
'PharmDr.' => array('doctor of pharmacy'), | |
'PhDr.' => array('doctor of philosophy'), | |
'PhMr.' => array('master of pharmacy'), | |
'RCDr.' => array('doctor of business studies'), | |
'RNDr.' => array('doctor of science'), | |
'DSc.' => array('doctor of science'), | |
'RSDr.' => array('doctor of socio-political sciences'), | |
'RTDr.' => array('doctor of technical sciences'), | |
'ThDr.' => array('doctor of theology'), | |
'Th.D.' => array('doctor of theology'), | |
'ThLic.' => array('licentiate of theology'), | |
'ThMgr.' => array('master of theology','master of divinity'), | |
'Acad.' => array('academian','academic'), | |
'ArtD.' => array('doctor of arts'), | |
'DiS.' => array('certified specialist'), | |
'As.' => array('assistant'), | |
'Odb. As.' => array('assistant professor'), | |
'Doc.' => array('associate professor'), | |
' ' => array('the') | |
), | |
'compound' => array( | |
'da','de','del','della', 'dem', 'den', 'der','di','du', 'het', 'la', 'onder', | |
'op', 'pietro','st.','st','\'t', 'ten', 'ter', 'van', 'vanden','vere','von' | |
), | |
'suffixes' => array( | |
'line' => array( | |
'I', 'II', 'III', 'IV', 'V', '1st', '2nd', '3rd', '4th', '5th', | |
'Senior', 'Junior', 'Jr', 'Sr' | |
), | |
'prof' => array( | |
'AO', 'B.A.', 'M.Sc', 'BCompt', 'PhD', 'Ph.D.','APR','RPh','PE','MD', | |
'M.D.', 'MA','DMD','CME', 'BSc', 'Bsc', 'BSc(hons)', 'Ph.D.', 'BEng', | |
'M.B.A.', 'MBA', 'FAICD', 'CM', 'OBC', 'M.B.', 'ChB', 'FRCP', 'FRSC', | |
'FREng', 'Esq', 'Esquire', 'MEng', 'MSc', 'J.D.', 'JD', 'BGDipBus', 'Dip', | |
'Dipl.Phys','M.H.Sc.', 'MPA', 'B.Comm', 'B.Eng', 'B.Acc', 'FSA', 'PGDM', | |
'FCPA', 'RN', 'R.N.', 'MSN', 'PCA', 'PCCRM','PCFP','PCGD','PCHR','PCM', | |
'PCPS','PCPM','PCSCM','PCSM','PCMM','PCTC','ACA', 'FCA','ACMA', | |
'FCMA', 'AAIA', 'FAIA', 'CCC', 'MIPA', 'FIPA', 'CIA', 'CFE', 'CISA', 'CFAP', | |
'QC', 'Q.C.', 'M.Tech', 'CTA', 'C.I.M.A.', 'B.Ec', 'CFIA','ICCP','CPS', | |
'CAP-OM','CAPTA','TNAOAP','AFA','AVA','ASA','CAIA','CBA','CVA','ICVS', | |
'CIIA','CMU','PFM','PRM','CFP','CWM','CCP','EA','CCMT','CGAP','CDFM', | |
'CFO','CGFM','CGAT','CGFO','CMFO','CPFO','CPFA', 'BMD','BIET', 'P.Eng', | |
'PE', 'MBBS', 'MB', 'BCh', 'BAO', 'BMBS', 'MBBChir', 'MBChBa','MPhil', | |
'LL.D', 'LLD', 'D.Lit','DEA', 'DESS', 'DClinPsy', 'DSc', 'MRes', | |
'M.Res', 'Psy.D', 'Pharm.D', 'BA(Admin)', 'BAcc','BACom','BAdmin', | |
'BAE','BAEcon','BA(Ed)','BA(FS)','BAgr','BAH','BAI','BAI(Elect)', | |
'BAI(Mech)','BALaw','BAO','BAppSc','BArch','BArchSc','BARelSt', | |
'BASc','BASoc', 'DDS', 'D.D.S.', 'BASS','BATheol','BBA','BBLS', 'BBS', | |
'BBus','BChem','BCJ','BCL','BCLD(SocSc)','BClinSci','BCom','BCombSt', | |
'BCommEdCommDev','BComp','BComSc','BCoun','BD','BDes','BE', 'BEcon', | |
'BEcon&Fin', 'M.P.P.M.', 'MPPM', 'BEconSci', 'BEd','BEng','BES', | |
'BEng(Tech)','BFA','BFin','BFLS','BFST','BH','BHealthSc','BHSc','BHy', | |
'BJur','BL','BLE','BLegSc','BLib','BLing','BLitt','BLittCelt','BLS', | |
'BMedSc','BMet', 'BMid', 'BMin','BMS','BMSc','BMSc','BMS','BMus', | |
'BMusEd','BMusPerf','BN', 'BNS','BNurs','BOptom','BPA','BPharm', | |
'BPhil', 'TTC', 'DIP', 'Tchg', 'BEd', 'MEd','ACIB', 'FCIM', 'FCIS', | |
'FCS', 'Fcs', 'Bachelor', 'O.C.', 'JP', 'C.Eng', 'C.P.A.', 'B.B.S.', | |
'MBE', 'GBE', 'KBE', 'DBE', 'CBE', 'OBE', 'MRICS', 'D.P.S.K.', | |
'D.P.P.J.', 'DPSK', 'DPPJ', 'B.B.A.', 'GBS', 'MIGEM', 'M.I.G.E.M.', | |
'FCIS', 'BPhil(Ed)', 'BPhys','BPhysio','BPl','BRadiog','BSc', 'B.Sc', | |
'BScAgr','BSc(Dairy)','BSc(DomSc)','BScEc','BScEcon','BSc(Econ)', | |
'BSc(Eng)','BScFor','BSc(HealthSc)','BSc(Hort)', 'BBA', 'B.B.A.', | |
'BSc(MCRM)', 'BSc(Med)','BSc(Mid)','BSc(Min)','BSc(Psych)', 'BSc(Tech)', | |
'BSD', 'BSocSc', 'BSS', 'BStSu', 'BTchg', 'BTCP', 'BTech', 'BTechEd', | |
'BTh', 'BTheol', 'BTS', 'EdB', 'LittB', 'LLB', 'MA', 'MusB', 'ScBTech', | |
'CEng', 'FCA', 'CFA', 'Cfa', 'C.F.A.', 'LLB', 'LL.B', 'LLM', 'LL.M', | |
'CA(SA)', 'C.A.', 'CA','CPA', 'Solicitor', 'DMS', 'FIWO', 'CEnv', | |
'MICE', 'MIWEM', 'B.Com', 'BCom', 'BAcc', 'BA', 'BEc', 'MEc', 'HDip', | |
'B.Bus.', 'E.S.C.P.' | |
) | |
), | |
'vowels' => array('a','e','i','o','u') | |
); | |
protected $not_nicknames = array( "(hons)"); | |
/** | |
* Parse Static entry point. | |
* | |
* @param string $name the full name you wish to parse | |
* @return array returns associative array of name parts | |
*/ | |
public static function parse($name) { | |
$parser = new self(); | |
return $parser->parse_name($name); | |
} | |
/** | |
* This is the primary method which calls all other methods | |
* | |
* @param string $name the full name you wish to parse | |
* @return array returns associative array of name parts | |
*/ | |
public function parse_name($full_name) { | |
# Remove leading/trailing whitespace | |
$full_name = trim($full_name); | |
// remove any words that don't add value | |
// $full_name = str_replace("(Hons)", '', $full_name ); | |
// $full_name = str_replace("(hons)", '', $full_name ); | |
# Setup default vars | |
extract(array( | |
'salutation' => '', 'fname' => '', 'initials' => '', 'lname' => '', | |
'lname_base' => '', 'lname_compound' => '', 'suffix' => '') | |
); | |
// Find all the professional suffixes possible | |
$professional_suffix = $this->get_pro_suffix($full_name); | |
// The position of the first professional suffix denotes the end of the name and the start of suffixes | |
$first_suffix_index = mb_strlen($full_name); | |
foreach ($professional_suffix as $key => $psx) { | |
$start = mb_strpos($full_name, $psx); | |
if ( $start === FALSE ) { | |
echo "ASSERT ERROR, the professional suffix:".$psx." cannot be found in the full name:".$full_name."<br>"; | |
continue; | |
} | |
if ( $start < $first_suffix_index) { | |
$first_suffix_index = $start; | |
} | |
} | |
// everything to the right of the first professional suffix is part of the suffix | |
$suffix = mb_substr($full_name, $first_suffix_index); | |
// remove the suffixes from the full_name | |
$full_name = mb_substr($full_name, 0, $first_suffix_index); | |
$full_name = rtrim($full_name, ','); // Trim off any trailing commas after suffix strip | |
# Deal with nickname, push to array | |
$has_nick = $this->get_nickname($full_name); | |
if ($has_nick) { | |
# Remove wrapper chars from around nickname | |
$name['nickname'] = mb_substr($has_nick, 1, (mb_strlen($has_nick) - 2)); | |
# Remove the nickname from the full name | |
$full_name = str_replace($has_nick, '', $full_name); | |
# Get rid of consecutive spaces left by the removal | |
$full_name = str_replace(' ', ' ', $full_name); | |
} | |
// SQ MOD >> Check for Last, First patterns | |
if (preg_match('/(.+), ([A-Z]\w+[\s\w.]*)$/', $full_name, $matches)) { | |
$new_name = array( | |
'first' => $matches[2], | |
'last' => $matches[1] | |
); | |
$full_name = rtrim($new_name['first'] . ' ' . $new_name['last'], ','); | |
} | |
// End SQ Mod | |
# Grab a list of words from the remainder of the full name | |
$unfiltered_name_parts = $this->break_words($full_name); | |
# Is first word a title or multiple titles consecutively? | |
if (count($unfiltered_name_parts)) { | |
// only start looking if there are any words left in the name to process | |
while (count($unfiltered_name_parts) > 0 && $s = $this->is_salutation($unfiltered_name_parts[0])) { | |
$salutation .= "$s "; | |
array_shift($unfiltered_name_parts); | |
} | |
$salutation = trim($salutation); | |
// Find if there is a line suffix, if so then move it out | |
# Is last word a suffix or multiple suffixes consecutively? | |
while (count($unfiltered_name_parts) > 0 && | |
$s = $this->is_line_suffix( | |
$unfiltered_name_parts[count($unfiltered_name_parts)-1], $full_name) | |
) { | |
if( $suffix != "") { | |
$suffix = $s.", ".$suffix; | |
} else { | |
$suffix .= $s; | |
} | |
array_pop($unfiltered_name_parts); | |
} | |
$suffix = trim($suffix); | |
} else { | |
$salutation = ""; | |
$suffix = ""; | |
} | |
// Re-pack the unfiltered name parts array and exclude empty words | |
$name_arr = array(); | |
foreach ($unfiltered_name_parts as $key => $name_part) { | |
$name_part = trim($name_part); | |
$name_part = rtrim($name_part,','); | |
if(mb_strlen($name_part) == '1') { | |
// If any word left is of one character that is not alphabetic then it is not a real word, so remove it | |
if( ! $this->mb_ctype_alpha($name_part)) { | |
$name_part = ""; | |
} | |
} | |
if(mb_strlen(trim($name_part)) ) { | |
$name_arr[] = $name_part; | |
} | |
} | |
$unfiltered_name_parts = $name_arr; | |
# set the ending range after prefix/suffix trim | |
$end = count($unfiltered_name_parts); | |
# concat the first name | |
for ($i=0; $i<$end-1; $i++) { | |
$word = $unfiltered_name_parts[$i]; | |
# move on to parsing the last name if we find an indicator of a compound last name (Von, Van, etc) | |
# we use $i != 0 to allow for rare cases where an indicator is actually the first name (like "Von Fabella") | |
if ($this->is_compound($word) && $i != 0) { | |
break; | |
} | |
# is it a middle initial or part of their first name? | |
# if we start off with an initial, we'll call it the first name | |
if ($this->is_initial($word)) { | |
# is the initial the first word? | |
if ($i == 0) { | |
# if so, do a look-ahead to see if they go by their middle name | |
# for ex: "R. Jason Smith" => "Jason Smith" & "R." is stored as an initial | |
# but "R. J. Smith" => "R. Smith" and "J." is stored as an initial | |
if ($this->is_initial($unfiltered_name_parts[$i+1])) { | |
$fname .= " ".mb_strtoupper($word); | |
} | |
else { | |
$initials .= " ".mb_strtoupper($word); | |
} | |
} | |
# otherwise, just go ahead and save the initial | |
else { | |
$initials .= " ".mb_strtoupper($word); | |
} | |
} | |
else { | |
$fname .= " ".$this->fix_case($word); | |
} | |
} | |
if( count($unfiltered_name_parts)) { | |
# check that we have more than 1 word in our string | |
if ($end-0 > 1) { | |
# concat the last name and split last name in base and compound | |
for ($i; $i < $end; $i++) { | |
if ($this->is_compound($unfiltered_name_parts[$i])) { | |
$lname_compound .= " ".$unfiltered_name_parts[$i]; | |
} else { | |
$lname_base .= " ".$this->fix_case($unfiltered_name_parts[$i]); | |
} | |
$lname .= " ".$this->fix_case($unfiltered_name_parts[$i]); | |
} | |
} | |
else { | |
# otherwise, single word strings are assumed to be first names | |
$fname = $this->fix_case($unfiltered_name_parts[$i]); | |
} | |
} else { | |
$fname = ""; | |
} | |
/*-------------------------------------------------------------- | |
# Last Check for oddness (SQ Mod) | |
--------------------------------------------------------------*/ | |
if (isset($new_name)) { | |
if (trim($lname) != trim($new_name['last'])) { | |
// There's some odd thing after a comma that isn't a last name | |
if (empty($suffix)) { // Set non-standard word as suffix if not set | |
$suffix = trim($new_name['first']); | |
} else { // Otherwise append to suffix | |
$suffix .= ', ' . trim($new_name['first']); | |
} | |
$regex = '/^' . trim($new_name['first']) .'/i'; | |
if (preg_match($regex, trim($fname))) { | |
$fname = preg_replace($regex, '', trim($fname)); // Trim off front name | |
} | |
} | |
} | |
/*-------------------------------------------------------------- | |
# Final Output | |
--------------------------------------------------------------*/ | |
//$name['full'] = $full_name; | |
$name['salutation'] = $salutation; | |
$name['fname'] = trim($fname); | |
$name['initials'] = trim($initials); | |
$name['lname'] = trim($lname); | |
// $name['lname_base'] = trim($lname_base); | |
// $name['lname_compound'] = trim($lname_compound); | |
$name['suffix'] = $suffix; | |
return $name; | |
} | |
/** | |
* Breaks name into individual words | |
* | |
* @param string $name the full name you wish to parse | |
* @return array full list of words broken down by spaces | |
*/ | |
public function break_words($name) { | |
$temp_word_arr = explode(' ', $name); | |
$final_word_arr = array(); | |
foreach ($temp_word_arr as $key => $word) { | |
if( $word != "" && $word != ",") { | |
$final_word_arr[] = $word; | |
} | |
} | |
return $final_word_arr; | |
} | |
/** | |
* Checks for the existence of, and returns professional suffix | |
* | |
* @param string $name the name you wish to test | |
* @return mixed returns the suffix if exists, false otherwise | |
*/ | |
public function get_pro_suffix(&$name) { | |
$found_suffix_arr = array(); | |
foreach ($this->dict['suffixes']['prof'] as $suffix) { | |
if (preg_match('/[,\s]+'.preg_quote($suffix).'\b/i', $name, $matches)) { | |
$found_suffix = trim($matches[0]); | |
$found_suffix = rtrim($found_suffix,','); | |
$found_suffix = ltrim($found_suffix,','); | |
$found_suffix_arr[] = trim($suffix); | |
$name = str_replace($found_suffix, $suffix, $name); | |
} | |
} | |
return $found_suffix_arr; | |
} | |
/** | |
* Function to check name for existence of nickname based on these stipulations | |
* - String wrapped in parentheses (string) | |
* - String wrapped in double quotes "string" | |
* x String wrapped in single quotes 'string' | |
* | |
* I removed the check for strings in single quotes 'string' due to possible | |
* conflicts with names that may include apostrophes. Arabic transliterations, for example | |
* | |
* @param string $name the name you wish to test against | |
* @return mixed returns nickname if exists, false otherwise | |
*/ | |
protected function get_nickname($name) { | |
if (preg_match("/[\(|\"].*?[\)|\"]/", $name, $matches)) { | |
if( ! in_array( mb_strtolower($matches[0]), $this->not_nicknames ) ) { | |
return $matches[0]; | |
} else { | |
return false; | |
} | |
} | |
return false; | |
} | |
/** | |
* Checks word against array of common lineage suffixes | |
* | |
* @param string $word the single word you wish to test | |
* @param string $name full name for context in determining edge-cases | |
* @return mixed boolean if false, string if true (returns suffix) | |
*/ | |
protected function is_line_suffix($word, $name) { | |
# Ignore periods and righ commas, normalize case | |
$word = str_replace('.', '', mb_strtolower($word)); | |
$word = rtrim($word,','); | |
# Search the array for our word | |
$line_match = array_search($word, array_map('mb_strtolower', $this->dict['suffixes']['line'])); | |
# Now test our edge cases based on lineage | |
if ($line_match !== false) { | |
# Store our match | |
$matched_case = $this->dict['suffixes']['line'][$line_match]; | |
# Remove it from the array | |
$temp_array = $this->dict['suffixes']['line']; | |
unset($temp_array[$line_match]); | |
# Make sure we're dealing with the suffix and not a surname | |
if ($word == 'senior' || $word == 'junior') { | |
# If name is Joshua Senior, it's pretty likely that Senior is the surname | |
# However, if the name is Joshua Jones Senior, then it's likely a suffix | |
if ($this->mb_str_word_count($name) < 3) { | |
return false; | |
} | |
# If the word Junior or Senior is contained, but so is some other | |
# lineage suffix, then the word is likely a surname and not a suffix | |
foreach ($temp_array as $suffix) { | |
if (preg_match("/\b".$suffix."\b/i", $name)) { | |
return false; | |
} | |
} | |
} | |
return $matched_case; | |
} | |
return false; | |
} | |
/** | |
* Checks word against list of common honorific prefixes | |
* | |
* @param string $word the single word you wish to test | |
* @return boolean | |
*/ | |
protected function is_salutation($word) { | |
$word = str_replace('.', '', mb_strtolower($word)); | |
foreach ($this->dict['prefix'] as $replace => $originals) { | |
if (in_array($word, $originals)) { | |
return $replace; | |
} | |
} | |
return false; | |
} | |
/** | |
* Checks our dictionary of compound indicators to see if last name is compound | |
* | |
* @param string $word the single word you wish to test | |
* @return boolean | |
*/ | |
protected function is_compound($word) { | |
return in_array(mb_strtolower($word), $this->dict['compound']); | |
} | |
/** | |
* Test string to see if it's a single letter/initial (period optional) | |
* | |
* @param string $word the single word you wish to test | |
* @return boolean | |
*/ | |
protected function is_initial($word) { | |
return ((mb_strlen($word) == 1) || (mb_strlen($word) == 2 && $word{1} == ".")); | |
} | |
/** | |
* Checks for camelCase words such as McDonald and MacElroy | |
* | |
* @param string $word the single word you wish to test | |
* @return boolean | |
*/ | |
protected function is_camel_case($word) { | |
if (preg_match('/\p{L}(\p{Lu}*\p{Ll}\p{Ll}*\p{Lu}|\p{Ll}*\p{Lu}\p{Lu}*\p{Ll})\p{L}*/', $word)) { | |
return true; | |
} | |
return false; | |
} | |
# ucfirst words split by dashes or periods | |
# ucfirst all upper/lower strings, but leave camelcase words alone | |
public function fix_case($word) { | |
# Fix case for words split by periods (J.P.) | |
if (mb_strpos($word, '.') !== false) { | |
$word = $this->safe_ucfirst(".", $word);; | |
} | |
# Fix case for words split by hyphens (Kimura-Fay) | |
if (mb_strpos($word, '-') !== false) { | |
$word = $this->safe_ucfirst("-", $word); | |
} | |
# Special case for single letters | |
if (mb_strlen($word) == 1) { | |
$word = mb_strtoupper($word); | |
} | |
# Special case for 2-letter words | |
if (mb_strlen($word) == 2) { | |
# Both letters vowels (uppercase both) | |
if (in_array(mb_strtolower($word{0}), $this->dict['vowels']) && | |
in_array(mb_strtolower($word{1}), $this->dict['vowels'])) { | |
$word = mb_strtoupper($word); | |
} | |
# Both letters consonants (uppercase both) | |
if (!in_array(mb_strtolower($word{0}), $this->dict['vowels']) && | |
!in_array(mb_strtolower($word{1}), $this->dict['vowels'])) { | |
$word = mb_strtoupper($word); | |
} | |
# First letter is vowel, second letter consonant (uppercase first) | |
if (in_array(mb_strtolower($word{0}), $this->dict['vowels']) && | |
!in_array(mb_strtolower($word{1}), $this->dict['vowels'])) { | |
$word = $this->mb_ucfirst(mb_strtolower($word)); | |
} | |
# First letter consonant, second letter vowel or "y" (uppercase first) | |
if (!in_array(mb_strtolower($word{0}), $this->dict['vowels']) && | |
(in_array(mb_strtolower($word{1}), $this->dict['vowels']) || | |
mb_strtolower($word{1}) == 'y')) { | |
$word = $this->mb_ucfirst(mb_strtolower($word)); | |
} | |
} | |
# Fix case for words which aren't initials, but are all uppercase or lowercase | |
// (Special case for Mc) | |
if (strtolower($word) == 'mc' || (mb_strlen($word) >= 3) && | |
($this->mb_ctype_upper($word) || $this->mb_ctype_lower($word))) { | |
$word = $this->mb_ucfirst(mb_strtolower($word)); | |
} | |
return $word; | |
} | |
# helper public function for fix_case | |
public function safe_ucfirst($seperator, $word) { | |
# uppercase words split by the seperator (ex. dashes or periods) | |
$parts = explode($seperator, $word); | |
foreach ($parts as $word) { | |
$words[] = ($this->is_camel_case($word)) ? $word : $this->mb_ucfirst(mb_strtolower($word)); | |
} | |
return implode($seperator, $words); | |
} | |
# helper public function for multibytes ctype_alpha | |
public function mb_ctype_alpha($text) { | |
return (bool)preg_match('/^\p{L}*$/', $text); | |
} | |
# helper public function for multibytes ctype_lower | |
public function mb_ctype_lower($text) { | |
return (bool)preg_match('/^\p{Ll}*$/', $text); | |
} | |
# helper public function for multibytes ctype_upper | |
public function mb_ctype_upper($text) { | |
return (bool)preg_match('/^\p{Lu}*$/', $text); | |
} | |
# helper public function for multibytes str_word_count | |
public function mb_str_word_count($text) { | |
if (empty($text)) { | |
return 0; | |
} else { | |
return str_word_count($text); | |
// OLD - Did not return correctly | |
//return preg_match('/s+/', $text) + 1; | |
} | |
} | |
# helper public function for multibytes ucfirst | |
public function mb_ucfirst($string) { | |
$strlen = mb_strlen($string); | |
$firstChar = mb_substr($string, 0, 1); | |
$then = mb_substr($string, 1, $strlen - 1); | |
return mb_strtoupper($firstChar) . $then; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment