Верхняя фамилия, за исключением строчной префиксной секции фамилии

Я пытаюсь определить метод для прописной фамилии; однако, исключая нижний префикс.

Пример имен и их преобразование:

  • MacArthur → MacARTHUR
  • McDavid → McDAVID
  • LeBlanc → LeBLANC
  • Макинтайр → McINTYRE
  • de Wit → de WIT

Есть также имена, которые будут содержать фамилии, которые должны быть полностью заглавные, поэтому простой функции для идентификации префикса, такого как strchr(), будет недостаточно:

  • Macmaster → MACMASTER
  • Macintosh → MACINTOSH

Функция PHP mb_strtoupper() не подходит, так как она набирает всю строку. Точно так же strtoupper() не подходит, а также теряет акценты на акцентированных именах.

Есть несколько ответов вокруг SO, которые частично отвечают на вопрос, например: Капитализация с использованием PHP Однако общий дефицит предполагает, что все имена с фамилией как Mac сопровождаются капиталом.

Имена правильно заглавные буквы в базе данных, поэтому мы можем предположить, что имя написано как Macarthur правильно, или MacArthur является правильным для другого человека.

Ответ 1

Следуя правилу, чтобы загладить все после прописной буквы last:

preg_replace_callback('/\p{Lu}\p{Ll}+$/u', 
                      function ($m) { return mb_strtoupper($m[0]); },
                      $name)

\p{Lu} и \p{Ll} являются символами верхнего и нижнего регистра Юникода соответственно, а mb_strtoupper - unicode известно... для простого варианта ASCII-варианта это тоже сделало бы:

preg_replace_callback('/[A-Z][a-z]+$/', 
                      function ($m) { return strtoupper($m[0]); },
                      $name)

Ответ 2

Я считаю, что это решение вопроса:

$names = array(
    'MacArthur',
    'Macarthur',
    'ÜtaTest',
    'de Wit'
);

$pattern = '~(?<prefix>(?:\p{Lu}.+|.+\s+))(?<suffix>\p{Lu}.*)~';
foreach ($names as $key => $name) {
    if (preg_match($pattern, $name, $matches)) {
        $names[$key] = $matches['prefix'] . mb_strtoupper($matches['suffix']);
    } else {
        $names[$key] = mb_strtoupper($name);
    }
}

print_r($names);

он производит следующий результат для входного массива выше:

Array
(
    [0] => MacARTHUR
    [1] => MACARTHUR
    [2] => ÜtaTEST
    [3] => de WIT
)

Краткое объяснение регулярного выражения:

(?<prefix>             # name of the captured group
   (?:                 # ignore this group
       \p{Lu}.+        # any uppercase character followed by any character
       |               # OR
       .+\s+           # any character followed by white space
   )
)
(?<suffix>             # name of the captured group
    \p{Lu}.*           # any uppercase character followed by any character
)

Ответ 3

Вот базовый алгоритм, который позволяет избежать критических регулярных выражений:

  • Создайте многобайтовый массив символов для литеральной фамилии (как это существует в базе данных).
  • Создайте второй массив символов в многозаходной заглавной форме.
  • Пересекайте оба массива, чтобы определить индекс конечного заглавного символа.
  • Объединить буквенную фамилию через индекс с заглавной формой после индекса.

В кодовой форме:

<?php
$names = [
    'MacArthur',
    'McDavid',
    'LeBlanc',
    'McIntyre',
    'de Wit',
    'Macmaster',
    'Macintosh',
    'MacMac',
    'die Über',
    'Van der Beek',
    'johnson',
    'Lindström',
    'Cehlárik',
];

// Uppercase after the last capital letter
function normalizeSurname($name) {
    // Split surname into a Unicode character array
    $chars = preg_split('//u', $name, -1, PREG_SPLIT_NO_EMPTY);

    // Capitalize surname and split into a character array
    $name_upper = mb_convert_case($name, MB_CASE_UPPER);
    $chars_upper = preg_split('//u', $name_upper, -1, PREG_SPLIT_NO_EMPTY);

    // Find the index of the last capitalize letter
    @$last_capital_idx = array_slice(array_keys(array_intersect($chars, $chars_upper)), -1)[0] ?: 0;

    // Concatenate the literal surname up to the index, and capitalized surname thereafter
    return mb_substr($name, 0, $last_capital_idx) . mb_substr($name_upper, $last_capital_idx);
}

// Loop through the surnames and display in normalized form
foreach($names as $name) {
    echo sprintf("%s -> %s\n", 
        $name,
        normalizeSurname($name)
    );
}

Вы получите вывод, например:

MacArthur -> MacARTHUR
McDavid -> McDAVID
LeBlanc -> LeBLANC
McIntyre -> McINTYRE
de Wit -> de WIT
Macmaster -> MACMASTER
Macintosh -> MACINTOSH
MacMac -> MacMAC
die Über -> die ÜBER
Van der Beek -> Van der BEEK
johnson -> JOHNSON
Lindström -> LINDSTRÖM
Cehlárik -> CEHLÁRIK

Это делает предположение о том, что заглавная буква целиком должна быть заглавной. Было бы легко изменить это поведение.

Ответ 4

  $string = "McBain";
  preg_match('/([A-Z][a-z]+\h*)$/', $string, $matches);
  /** 
   Added qualifier for if no match found
   **/
  if(!empty($matches[1])){
      // $upperString = str_replace($matches[1], strtoupper($matches[1]),$string);
      // replace only last occurance of string:
      $pos = strrpos($string, $matches[1]);
     if($pos !== false)
         {
         $upperString = substr_replace($string, strtoupper($matches[1]), $pos, strlen($matches[1]));
          }
  }
  else {
      $upperString = strtoupper($string);
  }
  print $upperString;

Результат:

$string = "McBain ";
$upperString = "McBAIN";

$string = "Mac Hartin";
$upperString = "Mac HARTIN";

$string = "Macaroni ";
$upperString = "MACARONI";

$string = "jacaroni";
$upperString = "JACARONI";

$string = "MacMac";
$upperString = "MacMAC";

(Также добавлено \h* в регулярное выражение, чтобы поймать любые пробелы.)

ссылка для поиска/замены последнего события.

Ответ 5

<?php
$string = "MacArthur";
$count = 0;
$finished = "";
$chars = str_split($string);
foreach($chars as $char){
    if(ctype_upper($char)){
        $count++;
    }
        if($count == 2){
          $finished .= strtoupper($char); 
        }
         else{
          $finished .= $char;  
            } 
} 
echo $finished; 

Ответ 6

Вот код для прописных букв всех символов после последнего верхнего регистра в строке.

preg_replace_callback('/[A-Z][^A-Z]+$/', function($match) {
  return strtoupper($match[0]);
}, $str);

Попробуйте использовать тестовые примеры из вашего вопроса: https://repl.it/NYcR/5

Ответ 7

Чтобы отличаться от остальных ответов, вы можете попробовать что-то вроде этого.

$names = array(
    'MacArthur',
    'Macarthur',
    'ÜtaTest',
    'de Wit'
);
function fixSurnameA($item) {
$lname = mb_strtolower($item);
$nameArrayA = str_split($item,1);
$nameArrayB = str_split($lname,1);
$result = array_diff($nameArrayA, $nameArrayB);
$keys = array_keys($result);
$key = max($keys);
if(count($keys)>=2 or (count($keys)==1 and $key>0)) {
$pre = substr($item, 0, $key);
$suf = mb_strtoupper(substr($item, $key));
echo $pre.$suf."\n";
} else {
 echo $item."\n";
}
}
function fixSurnameB($item) {
$lname = mb_strtolower($item);
$nameArrayA = str_split($item,1);
$nameArrayB = str_split($lname,1);
$result = array_diff($nameArrayA, $nameArrayB);
$keys = array_keys($result);
$key = max($keys);
$pre = substr($item, 0, $key);
$suf = mb_strtoupper(substr($item, $key));
echo $pre.$suf."\n";
}

array_walk($names,'fixSurnameA');
/* MacARTHUR
   Macarthur
   ÜtaTEST
   de WIT 
*/
array_walk($names,'fixSurnameB');
/* MacARTHUR
   MACARTHUR
   ÜtaTEST
   de WIT 
*/

Проверьте это на PHP SandBox