|
20 | 20 | use Sabre\VObject\Property\VCard\Date; |
21 | 21 |
|
22 | 22 | class Converter { |
| 23 | + private const NAME_SUFFIXES = [ |
| 24 | + 'i', 'ii', 'iii', 'iv', 'v', |
| 25 | + 'senior', 'junior', 'jr', 'sr', |
| 26 | + 'phd', 'apr', 'rph', 'pe', 'md', 'ma', 'msc', 'bsc', 'ba', 'bs', |
| 27 | + 'dmd', 'cme', 'bsn', 'mba', |
| 28 | + 'ceo', 'cto', 'cfo', 'coo', |
| 29 | + ]; |
| 30 | + private const NAME_SALUTATIONS = [ |
| 31 | + 'mr', 'mrs', 'ms', 'miss', 'master', 'mister', 'dr', 'rev', 'fr', 'prof', |
| 32 | + 'herr', 'frau', 'mme', 'mlle', 'me', 'pr', |
| 33 | + ]; |
| 34 | + |
23 | 35 | public function __construct( |
24 | 36 | private IAccountManager $accountManager, |
25 | 37 | private IUserManager $userManager, |
@@ -157,33 +169,87 @@ public function createCardFromUser(IUser $user): ?VCard { |
157 | 169 | } |
158 | 170 |
|
159 | 171 | public function splitFullName(string $fullName): array { |
160 | | - // Very basic western style parsing. I'm not gonna implement |
161 | | - // https://github.com/android/platform_packages_providers_contactsprovider/blob/master/src/com/android/providers/contacts/NameSplitter.java ;) |
162 | | - |
163 | | - // Handle "Lastname, Firstname" format |
164 | | - if (str_contains($fullName, ',')) { |
165 | | - [$family, $given] = array_map('trim', explode(',', $fullName, 2)); |
166 | | - if ($family !== '' && $given !== '') { |
167 | | - return [$family, $given, '', '', '']; |
| 172 | + // Based on https://github.com/joshfraser/PHP-Name-Parser |
| 173 | + |
| 174 | + $prefix = []; |
| 175 | + $suffix = []; |
| 176 | + $cleanedName = preg_replace('/\([^()]*\)|\[[^[\]]*\]|\{[^{}]*\}/', ' ', $fullName) ?? $fullName; |
| 177 | + $cleanedName = trim(preg_replace('/\s+/', ' ', $cleanedName) ?? $cleanedName); |
| 178 | + if ($cleanedName === '') { |
| 179 | + $cleanedName = trim($fullName); |
| 180 | + } |
| 181 | + |
| 182 | + $segments = array_values(array_filter( |
| 183 | + array_map($this->splitNameWords(...), explode(',', $cleanedName)), |
| 184 | + static fn (array $segment): bool => $segment !== [], |
| 185 | + )); |
| 186 | + |
| 187 | + while (count($segments) > 1) { |
| 188 | + $lastSegment = $segments[count($segments) - 1]; |
| 189 | + $knownSuffix = array_filter( |
| 190 | + $lastSegment, |
| 191 | + fn (string $word): bool => !$this->isNameSuffix($word), |
| 192 | + ) === []; |
| 193 | + if (!$knownSuffix) { |
| 194 | + break; |
168 | 195 | } |
| 196 | + array_unshift($suffix, implode(' ', array_pop($segments))); |
169 | 197 | } |
170 | 198 |
|
171 | | - $elements = explode(' ', $fullName); |
172 | | - $result = ['', '', '', '', '']; |
173 | | - if (count($elements) > 2) { |
174 | | - $result[0] = implode(' ', array_slice($elements, count($elements) - 1)); |
175 | | - $result[1] = $elements[0]; |
176 | | - $result[2] = implode(' ', array_slice($elements, 1, count($elements) - 2)); |
177 | | - } elseif (count($elements) === 2) { |
178 | | - $result[0] = $elements[1]; |
179 | | - $result[1] = $elements[0]; |
| 199 | + if (count($segments) > 1 && count($segments[0]) === 1) { |
| 200 | + $result = [$segments[0][0], '', '', '', '']; |
| 201 | + $nameWords = array_merge(...array_slice($segments, 1)); |
180 | 202 | } else { |
181 | | - $result[0] = $elements[0]; |
| 203 | + $result = ['', '', '', '', '']; |
| 204 | + $nameWords = $segments[0] ?? $this->splitNameWords($cleanedName); |
| 205 | + if (count($segments) > 1) { |
| 206 | + $suffix[] = implode(', ', array_map(static fn (array $segment): string => implode(' ', $segment), array_slice($segments, 1))); |
| 207 | + } |
| 208 | + } |
| 209 | + |
| 210 | + while (count($nameWords) > 1 && $this->isNameSalutation($nameWords[0])) { |
| 211 | + $prefix[] = array_shift($nameWords); |
| 212 | + } |
| 213 | + while (count($nameWords) > 1 && $this->isNameSuffix($nameWords[count($nameWords) - 1])) { |
| 214 | + array_unshift($suffix, array_pop($nameWords)); |
| 215 | + } |
| 216 | + |
| 217 | + if ($result[0] !== '') { |
| 218 | + $result[1] = $nameWords[0] ?? ''; |
| 219 | + $result[2] = implode(' ', array_slice($nameWords, 1)); |
| 220 | + } elseif (count($nameWords) > 2) { |
| 221 | + $result[0] = implode(' ', array_slice($nameWords, count($nameWords) - 1)); |
| 222 | + $result[1] = $nameWords[0]; |
| 223 | + $result[2] = implode(' ', array_slice($nameWords, 1, count($nameWords) - 2)); |
| 224 | + } elseif (count($nameWords) === 2) { |
| 225 | + $result[0] = $nameWords[1]; |
| 226 | + $result[1] = $nameWords[0]; |
| 227 | + } elseif (count($nameWords) === 1) { |
| 228 | + $result[0] = $nameWords[0]; |
182 | 229 | } |
183 | 230 |
|
| 231 | + $result[3] = implode(' ', $prefix); |
| 232 | + $result[4] = implode(', ', array_filter($suffix)); |
| 233 | + |
184 | 234 | return $result; |
185 | 235 | } |
186 | 236 |
|
| 237 | + private function splitNameWords(string $name): array { |
| 238 | + return preg_split('/\s+/', trim($name), -1, PREG_SPLIT_NO_EMPTY) ?: []; |
| 239 | + } |
| 240 | + |
| 241 | + private function isNameSuffix(string $word): bool { |
| 242 | + return in_array($this->normalizeNameWord($word), self::NAME_SUFFIXES, true); |
| 243 | + } |
| 244 | + |
| 245 | + private function isNameSalutation(string $word): bool { |
| 246 | + return in_array($this->normalizeNameWord($word), self::NAME_SALUTATIONS, true); |
| 247 | + } |
| 248 | + |
| 249 | + private function normalizeNameWord(string $word): string { |
| 250 | + return strtolower(preg_replace('/[.,]/', '', trim($word)) ?? $word); |
| 251 | + } |
| 252 | + |
187 | 253 | private function getAvatarImage(IUser $user): ?IImage { |
188 | 254 | try { |
189 | 255 | return $user->getAvatarImage(512); |
|
0 commit comments