mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2024-12-20 19:06:06 +01:00
347 lines
15 KiB
JavaScript
347 lines
15 KiB
JavaScript
|
||
|
||
// https://github.com/RateGravity/parse-full-name/blob/master/index.js
|
||
module.exports = (nameToParse, partToReturn, fixCase, stopOnError, useLongLists) => {
|
||
|
||
var i, j, k, l, m, n, part, comma, titleList, suffixList, prefixList, regex,
|
||
partToCheck, partFound, partsFoundCount, firstComma, remainingCommas,
|
||
nameParts = [], nameCommas = [null], partsFound = [],
|
||
conjunctionList = ['&', 'and', 'et', 'e', 'of', 'the', 'und', 'y'],
|
||
parsedName = {
|
||
title: '', first: '', middle: '', last: '', nick: '', suffix: '', error: []
|
||
};
|
||
|
||
// Validate inputs, or set to defaults
|
||
partToReturn = partToReturn && ['title', 'first', 'middle', 'last', 'nick',
|
||
'suffix', 'error'].indexOf(partToReturn.toLowerCase()) > -1 ?
|
||
partToReturn.toLowerCase() : 'all';
|
||
// 'all' = return object with all parts, others return single part
|
||
if (fixCase === false) fixCase = 0;
|
||
if (fixCase === true) fixCase = 1;
|
||
fixCase = fixCase !== 'undefined' && (fixCase === 0 || fixCase === 1) ?
|
||
fixCase : -1; // -1 = fix case only if input is all upper or lowercase
|
||
if (stopOnError === true) stopOnError = 1;
|
||
stopOnError = stopOnError && stopOnError === 1 ? 1 : 0;
|
||
// false = output warnings on parse error, but don't stop
|
||
if (useLongLists === true) useLongLists = 1;
|
||
useLongLists = useLongLists && useLongLists === 1 ? 1 : 0; // 0 = short lists
|
||
|
||
// If stopOnError = 1, throw error, otherwise return error messages in array
|
||
function handleError(errorMessage) {
|
||
if (stopOnError) {
|
||
throw 'Error: ' + errorMessage;
|
||
} else {
|
||
parsedName.error.push('Error: ' + errorMessage);
|
||
}
|
||
}
|
||
|
||
// If fixCase = 1, fix case of parsedName parts before returning
|
||
function fixParsedNameCase(fixedCaseName, fixCaseNow) {
|
||
var forceCaseList = ['e', 'y', 'av', 'af', 'da', 'dal', 'de', 'del', 'der', 'di',
|
||
'la', 'le', 'van', 'der', 'den', 'vel', 'von', 'II', 'III', 'IV', 'J.D.', 'LL.M.',
|
||
'M.D.', 'D.O.', 'D.C.', 'Ph.D.'];
|
||
var forceCaseListIndex;
|
||
var namePartLabels = [];
|
||
var namePartWords;
|
||
if (fixCaseNow) {
|
||
namePartLabels = Object.keys(parsedName)
|
||
.filter(function (v) { return v !== 'error'; });
|
||
for (i = 0, l = namePartLabels.length; i < l; i++) {
|
||
if (fixedCaseName[namePartLabels[i]]) {
|
||
namePartWords = (fixedCaseName[namePartLabels[i]] + '').split(' ');
|
||
for (j = 0, m = namePartWords.length; j < m; j++) {
|
||
forceCaseListIndex = forceCaseList
|
||
.map(function (v) { return v.toLowerCase(); })
|
||
.indexOf(namePartWords[j].toLowerCase());
|
||
if (forceCaseListIndex > -1) { // Set case of words in forceCaseList
|
||
namePartWords[j] = forceCaseList[forceCaseListIndex];
|
||
} else if (namePartWords[j].length === 1) { // Uppercase initials
|
||
namePartWords[j] = namePartWords[j].toUpperCase();
|
||
} else if (
|
||
namePartWords[j].length > 2 &&
|
||
namePartWords[j].slice(0, 1) ===
|
||
namePartWords[j].slice(0, 1).toUpperCase() &&
|
||
namePartWords[j].slice(1, 2) ===
|
||
namePartWords[j].slice(1, 2).toLowerCase() &&
|
||
namePartWords[j].slice(2) ===
|
||
namePartWords[j].slice(2).toUpperCase()
|
||
) { // Detect McCASE and convert to McCase
|
||
namePartWords[j] = namePartWords[j].slice(0, 3) +
|
||
namePartWords[j].slice(3).toLowerCase();
|
||
} else if (
|
||
namePartLabels[j] === 'suffix' &&
|
||
namePartWords[j].slice(-1) !== '.' &&
|
||
!suffixList.indexOf(namePartWords[j].toLowerCase())
|
||
) { // Convert suffix abbreviations to UPPER CASE
|
||
if (namePartWords[j] === namePartWords[j].toLowerCase()) {
|
||
namePartWords[j] = namePartWords[j].toUpperCase();
|
||
}
|
||
} else { // Convert to Title Case
|
||
namePartWords[j] = namePartWords[j].slice(0, 1).toUpperCase() +
|
||
namePartWords[j].slice(1).toLowerCase();
|
||
}
|
||
}
|
||
fixedCaseName[namePartLabels[i]] = namePartWords.join(' ');
|
||
}
|
||
}
|
||
}
|
||
return fixedCaseName;
|
||
}
|
||
|
||
// If no input name, or input name is not a string, abort
|
||
if (!nameToParse || typeof nameToParse !== 'string') {
|
||
handleError('No input');
|
||
parsedName = fixParsedNameCase(parsedName, fixCase);
|
||
return partToReturn === 'all' ? parsedName : parsedName[partToReturn];
|
||
} else {
|
||
nameToParse = nameToParse.trim();
|
||
}
|
||
|
||
// Auto-detect fixCase: fix if nameToParse is all upper or all lowercase
|
||
if (fixCase === -1) {
|
||
fixCase = (
|
||
nameToParse === nameToParse.toUpperCase() ||
|
||
nameToParse === nameToParse.toLowerCase() ? 1 : 0
|
||
);
|
||
}
|
||
|
||
// Initilize lists of prefixs, suffixs, and titles to detect
|
||
// Note: These list entries must be all lowercase
|
||
if (useLongLists) {
|
||
suffixList = ['esq', 'esquire', 'jr', 'jnr', 'sr', 'snr', '2', 'ii', 'iii', 'iv',
|
||
'v', 'clu', 'chfc', 'cfp', 'md', 'phd', 'j.d.', 'll.m.', 'm.d.', 'd.o.', 'd.c.',
|
||
'p.c.', 'ph.d.'];
|
||
prefixList = ['a', 'ab', 'antune', 'ap', 'abu', 'al', 'alm', 'alt', 'bab', 'bäck',
|
||
'bar', 'bath', 'bat', 'beau', 'beck', 'ben', 'berg', 'bet', 'bin', 'bint', 'birch',
|
||
'björk', 'björn', 'bjur', 'da', 'dahl', 'dal', 'de', 'degli', 'dele', 'del',
|
||
'della', 'der', 'di', 'dos', 'du', 'e', 'ek', 'el', 'escob', 'esch', 'fleisch',
|
||
'fitz', 'fors', 'gott', 'griff', 'haj', 'haug', 'holm', 'ibn', 'kauf', 'kil',
|
||
'koop', 'kvarn', 'la', 'le', 'lind', 'lönn', 'lund', 'mac', 'mhic', 'mic', 'mir',
|
||
'na', 'naka', 'neder', 'nic', 'ni', 'nin', 'nord', 'norr', 'ny', 'o', 'ua', 'ui\'',
|
||
'öfver', 'ost', 'över', 'öz', 'papa', 'pour', 'quarn', 'skog', 'skoog', 'sten',
|
||
'stor', 'ström', 'söder', 'ter', 'ter', 'tre', 'türk', 'van', 'väst', 'väster',
|
||
'vest', 'von'];
|
||
titleList = ['mr', 'mrs', 'ms', 'miss', 'dr', 'herr', 'monsieur', 'hr', 'frau',
|
||
'a v m', 'admiraal', 'admiral', 'air cdre', 'air commodore', 'air marshal',
|
||
'air vice marshal', 'alderman', 'alhaji', 'ambassador', 'baron', 'barones',
|
||
'brig', 'brig gen', 'brig general', 'brigadier', 'brigadier general',
|
||
'brother', 'canon', 'capt', 'captain', 'cardinal', 'cdr', 'chief', 'cik', 'cmdr',
|
||
'coach', 'col', 'col dr', 'colonel', 'commandant', 'commander', 'commissioner',
|
||
'commodore', 'comte', 'comtessa', 'congressman', 'conseiller', 'consul',
|
||
'conte', 'contessa', 'corporal', 'councillor', 'count', 'countess',
|
||
'crown prince', 'crown princess', 'dame', 'datin', 'dato', 'datuk',
|
||
'datuk seri', 'deacon', 'deaconess', 'dean', 'dhr', 'dipl ing', 'doctor',
|
||
'dott', 'dott sa', 'dr', 'dr ing', 'dra', 'drs', 'embajador', 'embajadora', 'en',
|
||
'encik', 'eng', 'eur ing', 'exma sra', 'exmo sr', 'f o', 'father',
|
||
'first lieutient', 'first officer', 'flt lieut', 'flying officer', 'fr',
|
||
'frau', 'fraulein', 'fru', 'gen', 'generaal', 'general', 'governor', 'graaf',
|
||
'gravin', 'group captain', 'grp capt', 'h e dr', 'h h', 'h m', 'h r h', 'hajah',
|
||
'haji', 'hajim', 'her highness', 'her majesty', 'herr', 'high chief',
|
||
'his highness', 'his holiness', 'his majesty', 'hon', 'hr', 'hra', 'ing', 'ir',
|
||
'jonkheer', 'judge', 'justice', 'khun ying', 'kolonel', 'lady', 'lcda', 'lic',
|
||
'lieut', 'lieut cdr', 'lieut col', 'lieut gen', 'lord', 'm', 'm l', 'm r',
|
||
'madame', 'mademoiselle', 'maj gen', 'major', 'master', 'mevrouw', 'miss',
|
||
'mlle', 'mme', 'monsieur', 'monsignor', 'mr', 'mrs', 'ms', 'mstr', 'nti', 'pastor',
|
||
'president', 'prince', 'princess', 'princesse', 'prinses', 'prof', 'prof dr',
|
||
'prof sir', 'professor', 'puan', 'puan sri', 'rabbi', 'rear admiral', 'rev',
|
||
'rev canon', 'rev dr', 'rev mother', 'reverend', 'rva', 'senator', 'sergeant',
|
||
'sheikh', 'sheikha', 'sig', 'sig na', 'sig ra', 'sir', 'sister', 'sqn ldr', 'sr',
|
||
'sr d', 'sra', 'srta', 'sultan', 'tan sri', 'tan sri dato', 'tengku', 'teuku',
|
||
'than puying', 'the hon dr', 'the hon justice', 'the hon miss', 'the hon mr',
|
||
'the hon mrs', 'the hon ms', 'the hon sir', 'the very rev', 'toh puan', 'tun',
|
||
'vice admiral', 'viscount', 'viscountess', 'wg cdr', 'ind', 'misc', 'mx'];
|
||
} else {
|
||
suffixList = ['esq', 'esquire', 'jr', 'jnr', 'sr', 'snr', '2', 'ii', 'iii', 'iv',
|
||
'md', 'phd', 'j.d.', 'll.m.', 'm.d.', 'd.o.', 'd.c.', 'p.c.', 'ph.d.'];
|
||
prefixList = ['ab', 'bar', 'bin', 'da', 'dal', 'de', 'de la', 'del', 'della', 'der',
|
||
'di', 'du', 'ibn', 'l\'', 'la', 'le', 'san', 'st', 'st.', 'ste', 'ter', 'van',
|
||
'van de', 'van der', 'van den', 'vel', 'ver', 'vere', 'von'];
|
||
titleList = ['dr', 'miss', 'mr', 'mrs', 'ms', 'prof', 'sir', 'frau', 'herr', 'hr',
|
||
'monsieur', 'captain', 'doctor', 'judge', 'officer', 'professor', 'ind', 'misc',
|
||
'mx'];
|
||
}
|
||
|
||
// Nickname: remove and store parts with surrounding punctuation as nicknames
|
||
regex = /\s(?:[‘’']([^‘’']+)[‘’']|[“”"]([^“”"]+)[“”"]|\[([^\]]+)\]|\(([^\)]+)\)),?\s/g;
|
||
partFound = (' ' + nameToParse + ' ').match(regex);
|
||
if (partFound) partsFound = partsFound.concat(partFound);
|
||
partsFoundCount = partsFound.length;
|
||
if (partsFoundCount === 1) {
|
||
parsedName.nick = partsFound[0].slice(2).slice(0, -2);
|
||
if (parsedName.nick.slice(-1) === ',') {
|
||
parsedName.nick = parsedName.nick.slice(0, -1);
|
||
}
|
||
nameToParse = (' ' + nameToParse + ' ').replace(partsFound[0], ' ').trim();
|
||
partsFound = [];
|
||
} else if (partsFoundCount > 1) {
|
||
handleError(partsFoundCount + ' nicknames found');
|
||
for (i = 0; i < partsFoundCount; i++) {
|
||
nameToParse = (' ' + nameToParse + ' ')
|
||
.replace(partsFound[i], ' ').trim();
|
||
partsFound[i] = partsFound[i].slice(2).slice(0, -2);
|
||
if (partsFound[i].slice(-1) === ',') {
|
||
partsFound[i] = partsFound[i].slice(0, -1);
|
||
}
|
||
}
|
||
parsedName.nick = partsFound.join(', ');
|
||
partsFound = [];
|
||
}
|
||
if (!nameToParse.trim().length) {
|
||
parsedName = fixParsedNameCase(parsedName, fixCase);
|
||
return partToReturn === 'all' ? parsedName : parsedName[partToReturn];
|
||
}
|
||
|
||
// Split remaining nameToParse into parts, remove and store preceding commas
|
||
for (i = 0, n = nameToParse.split(' '), l = n.length; i < l; i++) {
|
||
part = n[i];
|
||
comma = null;
|
||
if (part.slice(-1) === ',') {
|
||
comma = ',';
|
||
part = part.slice(0, -1);
|
||
}
|
||
nameParts.push(part);
|
||
nameCommas.push(comma);
|
||
}
|
||
|
||
// Suffix: remove and store matching parts as suffixes
|
||
for (l = nameParts.length, i = l - 1; i > 0; i--) {
|
||
partToCheck = (nameParts[i].slice(-1) === '.' ?
|
||
nameParts[i].slice(0, -1).toLowerCase() : nameParts[i].toLowerCase());
|
||
if (
|
||
suffixList.indexOf(partToCheck) > -1 ||
|
||
suffixList.indexOf(partToCheck + '.') > -1
|
||
) {
|
||
partsFound = nameParts.splice(i, 1).concat(partsFound);
|
||
if (nameCommas[i] === ',') { // Keep comma, either before or after
|
||
nameCommas.splice(i + 1, 1);
|
||
} else {
|
||
nameCommas.splice(i, 1);
|
||
}
|
||
}
|
||
}
|
||
partsFoundCount = partsFound.length;
|
||
if (partsFoundCount === 1) {
|
||
parsedName.suffix = partsFound[0];
|
||
partsFound = [];
|
||
} else if (partsFoundCount > 1) {
|
||
handleError(partsFoundCount + ' suffixes found');
|
||
parsedName.suffix = partsFound.join(', ');
|
||
partsFound = [];
|
||
}
|
||
if (!nameParts.length) {
|
||
parsedName = fixParsedNameCase(parsedName, fixCase);
|
||
return partToReturn === 'all' ? parsedName : parsedName[partToReturn];
|
||
}
|
||
|
||
// Title: remove and store matching parts as titles
|
||
for (l = nameParts.length, i = l - 1; i >= 0; i--) {
|
||
partToCheck = (nameParts[i].slice(-1) === '.' ?
|
||
nameParts[i].slice(0, -1).toLowerCase() : nameParts[i].toLowerCase());
|
||
if (
|
||
titleList.indexOf(partToCheck) > -1 ||
|
||
titleList.indexOf(partToCheck + '.') > -1
|
||
) {
|
||
partsFound = nameParts.splice(i, 1).concat(partsFound);
|
||
if (nameCommas[i] === ',') { // Keep comma, either before or after
|
||
nameCommas.splice(i + 1, 1);
|
||
} else {
|
||
nameCommas.splice(i, 1);
|
||
}
|
||
}
|
||
}
|
||
partsFoundCount = partsFound.length;
|
||
if (partsFoundCount === 1) {
|
||
parsedName.title = partsFound[0];
|
||
partsFound = [];
|
||
} else if (partsFoundCount > 1) {
|
||
handleError(partsFoundCount + ' titles found');
|
||
parsedName.title = partsFound.join(', ');
|
||
partsFound = [];
|
||
}
|
||
if (!nameParts.length) {
|
||
parsedName = fixParsedNameCase(parsedName, fixCase);
|
||
return partToReturn === 'all' ? parsedName : parsedName[partToReturn];
|
||
}
|
||
|
||
// Join name prefixes to following names
|
||
if (nameParts.length > 1) {
|
||
for (i = nameParts.length - 2; i >= 0; i--) {
|
||
if (prefixList.indexOf(nameParts[i].toLowerCase()) > -1) {
|
||
nameParts[i] = nameParts[i] + ' ' + nameParts[i + 1];
|
||
nameParts.splice(i + 1, 1);
|
||
nameCommas.splice(i + 1, 1);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Join conjunctions to surrounding names
|
||
if (nameParts.length > 2) {
|
||
for (i = nameParts.length - 3; i >= 0; i--) {
|
||
if (conjunctionList.indexOf(nameParts[i + 1].toLowerCase()) > -1) {
|
||
nameParts[i] = nameParts[i] + ' ' + nameParts[i + 1] + ' ' + nameParts[i + 2];
|
||
nameParts.splice(i + 1, 2);
|
||
nameCommas.splice(i + 1, 2);
|
||
i--;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Suffix: remove and store items after extra commas as suffixes
|
||
nameCommas.pop();
|
||
firstComma = nameCommas.indexOf(',');
|
||
remainingCommas = nameCommas.filter(function (v) { return v !== null; }).length;
|
||
if (firstComma > 1 || remainingCommas > 1) {
|
||
for (i = nameParts.length - 1; i >= 2; i--) {
|
||
if (nameCommas[i] === ',') {
|
||
partsFound = nameParts.splice(i, 1).concat(partsFound);
|
||
nameCommas.splice(i, 1);
|
||
remainingCommas--;
|
||
} else {
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
if (partsFound.length) {
|
||
if (parsedName.suffix) {
|
||
partsFound = [parsedName.suffix].concat(partsFound);
|
||
}
|
||
parsedName.suffix = partsFound.join(', ');
|
||
partsFound = [];
|
||
}
|
||
|
||
// Last name: remove and store last name
|
||
if (remainingCommas > 0) {
|
||
if (remainingCommas > 1) {
|
||
handleError((remainingCommas - 1) + ' extra commas found');
|
||
}
|
||
// Remove and store all parts before first comma as last name
|
||
if (nameCommas.indexOf(',')) {
|
||
parsedName.last = nameParts.splice(0, nameCommas.indexOf(',')).join(' ');
|
||
nameCommas.splice(0, nameCommas.indexOf(','));
|
||
}
|
||
} else {
|
||
// Remove and store last part as last name
|
||
parsedName.last = nameParts.pop();
|
||
}
|
||
if (!nameParts.length) {
|
||
parsedName = fixParsedNameCase(parsedName, fixCase);
|
||
return partToReturn === 'all' ? parsedName : parsedName[partToReturn];
|
||
}
|
||
|
||
// First name: remove and store first part as first name
|
||
parsedName.first = nameParts.shift();
|
||
if (!nameParts.length) {
|
||
parsedName = fixParsedNameCase(parsedName, fixCase);
|
||
return partToReturn === 'all' ? parsedName : parsedName[partToReturn];
|
||
}
|
||
|
||
// Middle name: store all remaining parts as middle name
|
||
if (nameParts.length > 2) {
|
||
handleError(nameParts.length + ' middle names');
|
||
}
|
||
parsedName.middle = nameParts.join(' ');
|
||
|
||
parsedName = fixParsedNameCase(parsedName, fixCase);
|
||
return partToReturn === 'all' ? parsedName : parsedName[partToReturn];
|
||
};
|