├── .gitignore ├── .travis.yml ├── package.json ├── README.md ├── test └── index.js └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | - "5" 5 | - "4" 6 | - "iojs" 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "parse-full-name", 3 | "version": "1.2.3", 4 | "description": "A function that accepts the full name of a person in any format, parses it, and returns its parts { title, first name, middle name, last name, nickname, suffix, [and any parsing errors] }.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha test/" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/dschnelldavis/parse-full-name" 12 | }, 13 | "keywords": [ 14 | "parse", 15 | "parser", 16 | "name", 17 | "full name", 18 | "first name", 19 | "middle name", 20 | "last name", 21 | "nickname" 22 | ], 23 | "author": "David Schnell-Davis (http://github.com/dschnelldavis)", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/dschnelldavis/parse-full-name/issues" 27 | }, 28 | "homepage": "https://github.com/dschnelldavis/parse-full-name", 29 | "dependencies": {}, 30 | "devDependencies": { 31 | "mocha": "^2.5.0" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # parse-full-name 2 | 3 | [![Build](https://travis-ci.org/dschnelldavis/parse-full-name.svg?branch=master)](https://travis-ci.org/dschnelldavis/parse-full-name) 4 | [![Dependencies](https://david-dm.org/dschnelldavis/parse-full-name.svg)](https://david-dm.org/dschnelldavis/parse-full-name) 5 | [![Vulnerabilities](https://snyk.io/test/npm/parse-full-name/badge.svg?style=flat-square)](https://snyk.io/test/npm/parse-full-name) 6 | 7 | ## Description 8 | 9 | parseFullName() is designed to parse large batches of full names in multiple 10 | inconsistent formats, as from a database, and continue processing without error, 11 | even if given some unparsable garbage entries. 12 | 13 | parseFullName(): 14 | 15 | 1. accepts a string containing a person's full name, in any format, 16 | 2. analyzes and attempts to detect the format of that name, 17 | 3. (if possible) parses the name into its component parts, and 18 | 4. (by default) returns an object containing all individual parts of the name: 19 | - title (string): title(s) (e.g. "Ms." or "Dr.") 20 | - first (string): first name or initial 21 | - middle (string): middle name(s) or initial(s) 22 | - last (string): last name or initial 23 | - nick (string): nickname(s) 24 | - suffix (string): suffix(es) (e.g. "Jr.", "II", or "Esq.") 25 | - error (array of strings): any parsing error messages 26 | 27 | Optionally, parseFullName() can also: 28 | 29 | * return only the specified part of a name as a string (or errors as an array) 30 | * always fix or ignore the letter case of the returned parts (the default is 31 | to fix the case only when the original input is all upper or all lowercase) 32 | * stop on errors (the default is to return warning messages in the output, 33 | but never throw a JavaScript error, no matter how mangled the input) 34 | * detect more variations of name prefixes, suffixes, and titles (the default 35 | detects 29 prefixes, 19 suffixes, 16 titles, and 8 conjunctions, but it 36 | can be set to detect 97 prefixes, 23 suffixes, and 204 titles instead) 37 | 38 | If this is not what you're looking for, is overkill for your application, or 39 | is in the wrong language, check the "Credits" section at the end of this file 40 | for links to several other excellent parsers which may suit your needs better. 41 | 42 | ## Use 43 | 44 | ### Basic Use 45 | 46 | ```javascript 47 | var parseFullName = require('parse-full-name').parseFullName; 48 | 49 | name = parseFullName('Mr. David Davis'); 50 | 51 | assert.equal(name.title, 'Mr.'); 52 | assert.equal(name.first, 'David'); 53 | assert.equal(name.last, 'Davis'); 54 | ``` 55 | 56 | ### Options 57 | 58 | parseFullName(nameToParse, partToReturn, fixCase, stopOnError, useLongLists) 59 | 60 | nameToParse (string, required): the name to be parsed 61 | 62 | partToReturn (string, optional): the name of a single part to return 63 | 64 | - 'all' (default) = return an object containing all name parts 65 | - 'title' = return only the title(s) as a string (or an empty string) 66 | - 'first' = return only the first name as a string (or an empty string) 67 | - 'middle' = return only the middle name(s) as a string (or an empty string) 68 | - 'last' = return only the last name as a string (or an empty string) 69 | - 'nick' = return only the nickname(s) as a string (or an empty string) 70 | - 'suffix' = return only the suffix(es) as a string (or an empty string) 71 | - 'error' = return only the array of parsing error messages (or an empty array) 72 | 73 | fixCase (integer, optional): fix case of output name 74 | 75 | - -1 (default) = fix case only if input name is all upper or lowercase 76 | - 0 or false = never fix the case (retain and output same case as input name) 77 | - 1 or true = always fix case of output, even if input is mixed case 78 | 79 | stopOnError (integer, optional): makes parsing errors throw JavaScript errors 80 | 81 | - 0 or false (default) = return warnings about parsing errors, but continue 82 | - 1 or true = if a parsing error is found, throw a JavaScript error 83 | 84 | useLongLists (integer, optional): use long prefix, suffix, and title lists 85 | 86 | - 0 or false (default) = use default lists (29 prefixes, 19 suffixes, 16 titles) 87 | - 1 or true = use experimental long lists (97 prefixes, 23 suffixes, 204 titles) 88 | Note: The alternate long lists are experimental and have not been tested. 89 | Be especially careful using the long prefix list, which may incorrectly 90 | detect "Ben" as a prefix, which is common in middle-eastern names, 91 | rather than as a first name, which is common in English names 92 | 93 | ### Advanced Use 94 | 95 | ```javascript 96 | var parseFullName = require('parse-full-name').parseFullName; 97 | 98 | name = parseFullName('DE LORENZO Y GUTIEREZ, Mr. JÜAN MARTINEZ (MARTIN) Jr.','all',1,0,0); 99 | 100 | assert.equal(name.title, 'Mr.'); 101 | assert.equal(name.first, 'Jüan'); 102 | assert.equal(name.middle, 'Martinez'); 103 | assert.equal(name.last, 'de Lorenzo y Gutierez'); 104 | assert.equal(name.nick, 'Martin'); 105 | assert.equal(name.suffix, 'Jr.'); 106 | assert.equal(name.error, []); 107 | ``` 108 | 109 | ## Reporting Bugs 110 | 111 | If you find a name this function does not parse correctly, or any other bug, 112 | please report it here: https://github.com/dschnelldavis/parse-full-name/issues 113 | 114 | ## Credits and precursors 115 | 116 | Before creating this function I studied many other name-parsing functions. 117 | None quite suited my needs, but many are excellent at what they do, and 118 | this function uses ideas from several of them. 119 | 120 | My thanks to all the following developers for sharing their work. 121 | 122 | "If I have seen further, it is by standing on the shoulders of giants." 123 | — Isaac Newton 124 | 125 | Josh Fraser's PHP-Name-Parser: 126 | https://github.com/joshfraser/PHP-Name-Parser 127 | 128 | Josh Fraser's JavaScript-Name-Parser: 129 | https://github.com/joshfraser/JavaScript-Name-Parser 130 | 131 | Garve Hays' Java NameParser: 132 | https://github.com/gkhays/NameParser 133 | 134 | Jason Priem's PHP HumanNameParser: 135 | https://web.archive.org/web/20150408022642/http://jasonpriem.org/human-name-parse/ and 136 | https://github.com/jasonpriem/HumanNameParser.php 137 | 138 | Keith Beckman's PHP nameparse: 139 | http://alphahelical.com/code/misc/nameparse/ 140 | 141 | Jed Hartman's PHP normalize_name: 142 | http://www.kith.org/journals/jed/2007/02/11/3813.html and 143 | http://www.kith.org/logos/things/code/name-parser-php.html 144 | 145 | ashaffer88's JavaScript parse-name: 146 | https://github.com/weo-edu/parse-name and 147 | https://www.npmjs.com/package/parse-name 148 | 149 | Derek Gulbranson's Python nameparser: 150 | https://github.com/derek73/python-nameparser/ 151 | 152 | Discussion about how to change all upper or lowercase names to correct case: 153 | http://stackoverflow.com/questions/11529213/given-upper-case-names-transform-to-proper-case-handling-ohara-mcdonald 154 | 155 | Title lists modified from: 156 | http://www.codeproject.com/Questions/262876/Titles-or-Salutation-list 157 | 158 | Suffix lists modified from: 159 | http://en.wikipedia.org/wiki/Suffix_(name) and 160 | https://github.com/derek73/python-nameparser/blob/master/nameparser/config/suffixes.py 161 | 162 | Prefix lists modified from: 163 | http://en.wikipedia.org/wiki/List_of_family_name_affixes 164 | 165 | Conjunction list copied entirely from: 166 | https://github.com/derek73/python-nameparser/blob/master/nameparser/config/conjunctions.py 167 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var parseFullName = require('../').parseFullName; 3 | var error; 4 | 5 | var verifyName = function(nameToCheck, partsToCheck) { 6 | assert.equal(nameToCheck.title, partsToCheck[0]); 7 | assert.equal(nameToCheck.first, partsToCheck[1]); 8 | assert.equal(nameToCheck.middle, partsToCheck[2]); 9 | assert.equal(nameToCheck.last, partsToCheck[3]); 10 | assert.equal(nameToCheck.nick, partsToCheck[4]); 11 | assert.equal(nameToCheck.suffix, partsToCheck[5]); 12 | assert.equal(nameToCheck.error.length, partsToCheck[6].length); 13 | for ( var i = 1, l = partsToCheck[6].length; i < l; i++ ) { 14 | assert.equal(nameToCheck.error[i], partsToCheck[6][i]); 15 | } 16 | }; 17 | 18 | describe('parse-full-name', function() { 19 | describe('parseFullName', function() { 20 | it('parses first names', function() { 21 | verifyName(parseFullName('David Davis'), 22 | ['','David','','Davis','','',[]]); 23 | verifyName(parseFullName('Davis, David'), 24 | ['','David','','Davis','','',[]]); 25 | }); 26 | it('parses last names', function() { 27 | verifyName(parseFullName('Gerald Böck'), 28 | ['','Gerald','','Böck','','',[]]); 29 | verifyName(parseFullName('Böck, Gerald'), 30 | ['','Gerald','','Böck','','',[]]); 31 | }); 32 | it('parses middle names', function() { 33 | verifyName(parseFullName('David William Davis'), 34 | ['','David','William','Davis','','',[]]); 35 | verifyName(parseFullName('Davis, David William'), 36 | ['','David','William','Davis','','',[]]); 37 | }); 38 | it('parses last names including known prefixes', function() { 39 | verifyName(parseFullName('Vincent Van Gogh'), 40 | ['','Vincent','','Van Gogh','','',[]]); 41 | verifyName(parseFullName('Van Gogh, Vincent'), 42 | ['','Vincent','','Van Gogh','','',[]]); 43 | verifyName(parseFullName('Lorenzo de Médici'), 44 | ['','Lorenzo','','de Médici','','',[]]); 45 | verifyName(parseFullName('de Médici, Lorenzo'), 46 | ['','Lorenzo','','de Médici','','',[]]); 47 | verifyName(parseFullName('Jüan de la Véña'), 48 | ['','Jüan','','de la Véña','','',[]]); 49 | verifyName(parseFullName('de la Véña, Jüan'), 50 | ['','Jüan','','de la Véña','','',[]]); 51 | }); 52 | it('parses compound last names', function() { 53 | verifyName(parseFullName('Jüan Martinez de Lorenzo y Gutierez'), 54 | ['','Jüan','Martinez','de Lorenzo y Gutierez','','',[]]); 55 | verifyName(parseFullName('de Lorenzo y Gutierez, Jüan Martinez'), 56 | ['','Jüan','Martinez','de Lorenzo y Gutierez','','',[]]); 57 | }); 58 | it('parses nicknames', function() { 59 | verifyName(parseFullName('Orenthal James "O. J." Simpson'), 60 | ['','Orenthal','James','Simpson','O. J.','',[]]); 61 | verifyName(parseFullName("Orenthal 'O. J.' James Simpson"), 62 | ['','Orenthal','James','Simpson','O. J.','',[]]); 63 | verifyName(parseFullName('(O. J.) Orenthal James Simpson'), 64 | ['','Orenthal','James','Simpson','O. J.','',[]]); 65 | verifyName(parseFullName('Simpson, Orenthal James “O. J.”'), 66 | ['','Orenthal','James','Simpson','O. J.','',[]]); 67 | verifyName(parseFullName("Simpson, Orenthal ‘O. J.’ James"), 68 | ['','Orenthal','James','Simpson','O. J.','',[]]); 69 | verifyName(parseFullName('Simpson, [O. J.] Orenthal James'), 70 | ['','Orenthal','James','Simpson','O. J.','',[]]); 71 | }); 72 | it('parses known suffixes', function() { 73 | verifyName(parseFullName('Sammy Davis, Jr.'), 74 | ['','Sammy','','Davis','','Jr.',[]]); 75 | verifyName(parseFullName('Davis, Sammy, Jr.'), 76 | ['','Sammy','','Davis','','Jr.',[]]); 77 | }); 78 | it('parses unknown suffixes', function() { 79 | verifyName(parseFullName('John P. Doe-Ray, Jr., CLU, CFP, LUTC'), 80 | ['','John','P.','Doe-Ray','','Jr., CLU, CFP, LUTC',[]]); 81 | verifyName(parseFullName('Doe-Ray, John P., Jr., CLU, CFP, LUTC'), 82 | ['','John','P.','Doe-Ray','','Jr., CLU, CFP, LUTC',[]]); 83 | }); 84 | it('parses titles', function() { 85 | verifyName(parseFullName('Dr. John P. Doe-Ray, Jr.'), 86 | ['Dr.','John','P.','Doe-Ray','','Jr.',[]]); 87 | verifyName(parseFullName('Dr. Doe-Ray, John P., Jr.'), 88 | ['Dr.','John','P.','Doe-Ray','','Jr.',[]]); 89 | verifyName(parseFullName('Doe-Ray, Dr. John P., Jr.'), 90 | ['Dr.','John','P.','Doe-Ray','','Jr.',[]]); 91 | }); 92 | it('parses name parts in many different orders', function() { 93 | verifyName(parseFullName( 94 | 'Mr. Jüan Martinez (Martin) de Lorenzo y Gutierez Jr.'), 95 | ['Mr.','Jüan','Martinez','de Lorenzo y Gutierez','Martin','Jr.',[]]); 96 | verifyName(parseFullName( 97 | 'de Lorenzo y Gutierez, Mr. Jüan Martinez (Martin) Jr.'), 98 | ['Mr.','Jüan','Martinez','de Lorenzo y Gutierez','Martin','Jr.',[]]); 99 | verifyName(parseFullName( 100 | 'de Lorenzo y Gutierez, Mr. Jüan (Martin) Martinez Jr.'), 101 | ['Mr.','Jüan','Martinez','de Lorenzo y Gutierez','Martin','Jr.',[]]); 102 | verifyName(parseFullName( 103 | 'Mr. de Lorenzo y Gutierez, Jüan Martinez (Martin) Jr.'), 104 | ['Mr.','Jüan','Martinez','de Lorenzo y Gutierez','Martin','Jr.',[]]); 105 | verifyName(parseFullName( 106 | 'Mr. de Lorenzo y Gutierez, Jüan (Martin) Martinez Jr.'), 107 | ['Mr.','Jüan','Martinez','de Lorenzo y Gutierez','Martin','Jr.',[]]); 108 | verifyName(parseFullName( 109 | 'Mr. de Lorenzo y Gutierez Jr., Jüan Martinez (Martin)'), 110 | ['Mr.','Jüan','Martinez','de Lorenzo y Gutierez','Martin','Jr.',[]]); 111 | verifyName(parseFullName( 112 | 'Mr. de Lorenzo y Gutierez Jr., Jüan (Martin) Martinez'), 113 | ['Mr.','Jüan','Martinez','de Lorenzo y Gutierez','Martin','Jr.',[]]); 114 | verifyName(parseFullName( 115 | 'Mr. de Lorenzo y Gutierez, Jr. Jüan Martinez (Martin)'), 116 | ['Mr.','Jüan','Martinez','de Lorenzo y Gutierez','Martin','Jr.',[]]); 117 | verifyName(parseFullName( 118 | 'Mr. de Lorenzo y Gutierez, Jr. Jüan (Martin) Martinez'), 119 | ['Mr.','Jüan','Martinez','de Lorenzo y Gutierez','Martin','Jr.',[]]); 120 | }); 121 | it('automatically fixes all upper and all lowercase names', function() { 122 | verifyName(parseFullName( 123 | 'MR. JÜAN MARTINEZ (MARTIN) DE LORENZO Y GUTIEREZ JR.'), 124 | ['Mr.','Jüan','Martinez','de Lorenzo y Gutierez','Martin','Jr.',[]]); 125 | verifyName(parseFullName( 126 | 'mr. jüan martinez (martin) de lorenzo y gutierez jr.'), 127 | ['Mr.','Jüan','Martinez','de Lorenzo y Gutierez','Martin','Jr.',[]]); 128 | }); 129 | it('manually fixes case, or not, when specified', function() { 130 | verifyName(parseFullName( 131 | 'Mr. JÜAN MARTINEZ (MARTIN) DE LORENZO Y GUTIEREZ Jr.'), 132 | ['Mr.','JÜAN','MARTINEZ','DE LORENZO Y GUTIEREZ','MARTIN','Jr.',[]]); 133 | verifyName(parseFullName( 134 | 'Mr. JÜAN MARTINEZ (MARTIN) DE LORENZO Y GUTIEREZ JR.','all',1), 135 | ['Mr.','Jüan','Martinez','de Lorenzo y Gutierez','Martin','Jr.',[]]); 136 | verifyName(parseFullName( 137 | 'mr. jüan martinez (martin) de lorenzo y gutierez jr.','all',0), 138 | ['mr.','jüan','martinez','de lorenzo y gutierez','martin','jr.',[]]); 139 | }); 140 | it('returns a single part, when specified', function() { 141 | assert.equal(parseFullName( 142 | 'Mr. Jüan Martinez (Martin) de Lorenzo y Gutierez Jr.', 143 | 'title'),'Mr.'); 144 | assert.equal(parseFullName( 145 | 'Mr. Jüan Martinez (Martin) de Lorenzo y Gutierez Jr.', 146 | 'first'),'Jüan'); 147 | assert.equal(parseFullName( 148 | 'Mr. Jüan Martinez (Martin) de Lorenzo y Gutierez Jr.', 149 | 'middle'),'Martinez'); 150 | assert.equal(parseFullName( 151 | 'Mr. Jüan Martinez (Martin) de Lorenzo y Gutierez Jr.', 152 | 'last'),'de Lorenzo y Gutierez'); 153 | assert.equal(parseFullName( 154 | 'Mr. Jüan Martinez (Martin) de Lorenzo y Gutierez Jr.', 155 | 'nick'),'Martin'); 156 | assert.equal(parseFullName( 157 | 'Mr. Jüan Martinez (Martin) de Lorenzo y Gutierez Jr.', 158 | 'suffix'),'Jr.'); 159 | }); 160 | it('continues processing, even when fed garbage input', function() { 161 | verifyName(parseFullName('as;dfkj ;aerha;sfa ef;oia;woeig hz;sofi hz;oifj;zoseifj zs;eofij z;soeif jzs;oefi jz;osif z;osefij zs;oif jz;soefihz;sodifh z;sofu hzsieufh zlsiudfh zksefiulzseofih ;zosufh ;oseihgfz;osef h:OSfih lziusefhaowieufyg oaweifugy'), 162 | ['','as;dfkj', 163 | ';aerha;sfa ef;oia;woeig hz;sofi hz;oifj;zoseifj zs;eofij z;soeif jzs;oefi jz;osif z;osefij zs;oif jz;soefihz;sodifh z;sofu hzsieufh zlsiudfh zksefiulzseofih ;zosufh ;oseihgfz;osef h:OSfih lziusefhaowieufyg', 164 | 'oaweifugy','','',['Error: 22 middle names']]); 165 | }); 166 | it('returns warnings for null/undefined names', function() { 167 | verifyName(parseFullName(null),['','','','','','',['Error: No input']]); 168 | verifyName(parseFullName(),['','','','','','',['Error: No input']]); 169 | }); 170 | it('will throw errors, when specified', function() { 171 | assert.doesNotThrow(function(){ return parseFullName(''); }); 172 | assert.throws(function(){ return parseFullName('','all',-1,1); }); 173 | }); 174 | }); 175 | }); 176 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | exports.parseFullName = function parseFullName( 2 | nameToParse, partToReturn, fixCase, stopOnError, useLongLists 3 | ) { 4 | "use strict"; 5 | 6 | var i, j, k, l, m, n, part, comma, titleList, suffixList, prefixList, regex, 7 | partToCheck, partFound, partsFoundCount, firstComma, remainingCommas, 8 | nameParts = [], nameCommas = [null], partsFound = [], 9 | conjunctionList = ['&','and','et','e','of','the','und','y'], 10 | parsedName = { 11 | title: '', first: '', middle: '', last: '', nick: '', suffix: '', error: [] 12 | }; 13 | 14 | // Validate inputs, or set to defaults 15 | partToReturn = partToReturn && ['title','first','middle','last','nick', 16 | 'suffix','error'].indexOf(partToReturn.toLowerCase()) > -1 ? 17 | partToReturn.toLowerCase() : 'all'; 18 | // 'all' = return object with all parts, others return single part 19 | if ( fixCase === false ) fixCase = 0; 20 | if ( fixCase === true ) fixCase = 1; 21 | fixCase = fixCase !== 'undefined' && ( fixCase === 0 || fixCase === 1 ) ? 22 | fixCase : -1; // -1 = fix case only if input is all upper or lowercase 23 | if ( stopOnError === true ) stopOnError = 1; 24 | stopOnError = stopOnError && stopOnError === 1 ? 1 : 0; 25 | // false = output warnings on parse error, but don't stop 26 | if ( useLongLists === true ) useLongLists = 1; 27 | useLongLists = useLongLists && useLongLists === 1 ? 1 : 0; // 0 = short lists 28 | 29 | // If stopOnError = 1, throw error, otherwise return error messages in array 30 | function handleError( errorMessage ) { 31 | if ( stopOnError ) { 32 | throw 'Error: ' + errorMessage; 33 | } else { 34 | parsedName.error.push('Error: ' + errorMessage); 35 | } 36 | } 37 | 38 | // If fixCase = 1, fix case of parsedName parts before returning 39 | function fixParsedNameCase ( fixedCaseName, fixCaseNow ) { 40 | var forceCaseList = ['e','y','av','af','da','dal','de','del','der','di', 41 | 'la','le','van','der','den','vel','von','II','III','IV','J.D.','LL.M.', 42 | 'M.D.','D.O.','D.C.','Ph.D.']; 43 | var forceCaseListIndex; 44 | var namePartLabels = []; 45 | var namePartWords; 46 | if (fixCaseNow) { 47 | namePartLabels = Object.keys(parsedName) 48 | .filter( function(v) { return v !== 'error'; } ); 49 | for ( i = 0, l = namePartLabels.length; i < l; i++ ) { 50 | if ( fixedCaseName[namePartLabels[i]] ) { 51 | namePartWords = ( fixedCaseName[namePartLabels[i]] + '' ).split(' '); 52 | for ( j = 0, m = namePartWords.length; j < m; j++ ) { 53 | forceCaseListIndex = forceCaseList 54 | .map( function(v) { return v.toLowerCase(); } ) 55 | .indexOf(namePartWords[j].toLowerCase()); 56 | if ( forceCaseListIndex > -1 ) { // Set case of words in forceCaseList 57 | namePartWords[j] = forceCaseList[forceCaseListIndex]; 58 | } else if ( namePartWords[j].length === 1 ) { // Uppercase initials 59 | namePartWords[j] = namePartWords[j].toUpperCase(); 60 | } else if ( 61 | namePartWords[j].length > 2 && 62 | namePartWords[j].slice(0,1) === 63 | namePartWords[j].slice(0,1).toUpperCase() && 64 | namePartWords[j].slice(1,2) === 65 | namePartWords[j].slice(1,2).toLowerCase() && 66 | namePartWords[j].slice(2) === 67 | namePartWords[j].slice(2).toUpperCase() 68 | ) { // Detect McCASE and convert to McCase 69 | namePartWords[j] = namePartWords[j].slice(0,3) + 70 | namePartWords[j].slice(3).toLowerCase(); 71 | } else if ( 72 | namePartLabels[j] === 'suffix' && 73 | nameParts[j].slice(-1) !== '.' && 74 | !suffixList.indexOf(nameParts[j].toLowerCase()) 75 | ) { // Convert suffix abbreviations to UPPER CASE 76 | if ( namePartWords[j] === namePartWords[j].toLowerCase() ) { 77 | namePartWords[j] = namePartWords[j].toUpperCase(); 78 | } 79 | } else { // Convert to Title Case 80 | namePartWords[j] = namePartWords[j].slice(0,1).toUpperCase() + 81 | namePartWords[j].slice(1).toLowerCase(); 82 | } 83 | } 84 | fixedCaseName[namePartLabels[i]] = namePartWords.join(' '); 85 | } 86 | } 87 | } 88 | return fixedCaseName; 89 | } 90 | 91 | // If no input name, or input name is not a string, abort 92 | if ( !nameToParse || typeof nameToParse !== 'string' ) { 93 | handleError('No input'); 94 | parsedName = fixParsedNameCase(parsedName, fixCase); 95 | return partToReturn === 'all' ? parsedName : parsedName[partToReturn]; 96 | } 97 | 98 | // Auto-detect fixCase: fix if nameToParse is all upper or all lowercase 99 | if ( fixCase === -1 ) { 100 | fixCase = ( 101 | nameToParse === nameToParse.toUpperCase() || 102 | nameToParse === nameToParse.toLowerCase() ? 1 : 0 103 | ); 104 | } 105 | 106 | // Initilize lists of prefixs, suffixs, and titles to detect 107 | // Note: These list entries must be all lowercase 108 | if ( useLongLists ) { 109 | suffixList = ['esq','esquire','jr','jnr','sr','snr','2','ii','iii','iv', 110 | 'v','clu','chfc','cfp','md','phd','j.d.','ll.m.','m.d.','d.o.','d.c.', 111 | 'p.c.','ph.d.']; 112 | prefixList = ['a','ab','antune','ap','abu','al','alm','alt','bab','bäck', 113 | 'bar','bath','bat','beau','beck','ben','berg','bet','bin','bint','birch', 114 | 'björk','björn','bjur','da','dahl','dal','de','degli','dele','del', 115 | 'della','der','di','dos','du','e','ek','el','escob','esch','fleisch', 116 | 'fitz','fors','gott','griff','haj','haug','holm','ibn','kauf','kil', 117 | 'koop','kvarn','la','le','lind','lönn','lund','mac','mhic','mic','mir', 118 | 'na','naka','neder','nic','ni','nin','nord','norr','ny','o','ua','ui\'', 119 | 'öfver','ost','över','öz','papa','pour','quarn','skog','skoog','sten', 120 | 'stor','ström','söder','ter','ter','tre','türk','van','väst','väster', 121 | 'vest','von']; 122 | titleList = ['mr','mrs','ms','miss','dr','herr','monsieur','hr','frau', 123 | 'a v m','admiraal','admiral','air cdre','air commodore','air marshal', 124 | 'air vice marshal','alderman','alhaji','ambassador','baron','barones', 125 | 'brig','brig gen','brig general','brigadier','brigadier general', 126 | 'brother','canon','capt','captain','cardinal','cdr','chief','cik','cmdr', 127 | 'coach','col','col dr','colonel','commandant','commander','commissioner', 128 | 'commodore','comte','comtessa','congressman','conseiller','consul', 129 | 'conte','contessa','corporal','councillor','count','countess', 130 | 'crown prince','crown princess','dame','datin','dato','datuk', 131 | 'datuk seri','deacon','deaconess','dean','dhr','dipl ing','doctor', 132 | 'dott','dott sa','dr','dr ing','dra','drs','embajador','embajadora','en', 133 | 'encik','eng','eur ing','exma sra','exmo sr','f o','father', 134 | 'first lieutient','first officer','flt lieut','flying officer','fr', 135 | 'frau','fraulein','fru','gen','generaal','general','governor','graaf', 136 | 'gravin','group captain','grp capt','h e dr','h h','h m','h r h','hajah', 137 | 'haji','hajim','her highness','her majesty','herr','high chief', 138 | 'his highness','his holiness','his majesty','hon','hr','hra','ing','ir', 139 | 'jonkheer','judge','justice','khun ying','kolonel','lady','lcda','lic', 140 | 'lieut','lieut cdr','lieut col','lieut gen','lord','m','m l','m r', 141 | 'madame','mademoiselle','maj gen','major','master','mevrouw','miss', 142 | 'mlle','mme','monsieur','monsignor','mr','mrs','ms','mstr','nti','pastor', 143 | 'president','prince','princess','princesse','prinses','prof','prof dr', 144 | 'prof sir','professor','puan','puan sri','rabbi','rear admiral','rev', 145 | 'rev canon','rev dr','rev mother','reverend','rva','senator','sergeant', 146 | 'sheikh','sheikha','sig','sig na','sig ra','sir','sister','sqn ldr','sr', 147 | 'sr d','sra','srta','sultan','tan sri','tan sri dato','tengku','teuku', 148 | 'than puying','the hon dr','the hon justice','the hon miss','the hon mr', 149 | 'the hon mrs','the hon ms','the hon sir','the very rev','toh puan','tun', 150 | 'vice admiral','viscount','viscountess','wg cdr', 'ind', 'misc', 'mx']; 151 | } else { 152 | suffixList = ['esq','esquire','jr','jnr','sr','snr','2','ii','iii','iv', 153 | 'md','phd','j.d.','ll.m.','m.d.','d.o.','d.c.','p.c.','ph.d.']; 154 | prefixList = ['ab','bar','bin','da','dal','de','de la','del','della','der', 155 | 'di','du','ibn','l\'','la','le','san','st','st.','ste','ter','van', 156 | 'van de','van der','van den','vel','ver','vere','von']; 157 | titleList = ['dr','miss','mr','mrs','ms','prof','sir','frau','herr','hr', 158 | 'monsieur','captain','doctor','judge','officer','professor', 'ind', 'misc', 159 | 'mx']; 160 | } 161 | 162 | // Nickname: remove and store parts with surrounding punctuation as nicknames 163 | regex = /\s(?:[‘’']([^‘’']+)[‘’']|[“”"]([^“”"]+)[“”"]|\[([^\]]+)\]|\(([^\)]+)\)),?\s/g; 164 | partFound = (' '+nameToParse+' ').match(regex); 165 | if ( partFound ) partsFound = partsFound.concat(partFound); 166 | partsFoundCount = partsFound.length; 167 | if ( partsFoundCount === 1 ) { 168 | parsedName.nick = partsFound[0].slice(2).slice(0,-2); 169 | if ( parsedName.nick.slice(-1) === ',' ) { 170 | parsedName.nick = parsedName.nick.slice(0,-1); 171 | } 172 | nameToParse = (' '+nameToParse+' ').replace(partsFound[0], ' ').trim(); 173 | partsFound = []; 174 | } else if ( partsFoundCount > 1 ) { 175 | handleError( partsFoundCount + ' nicknames found' ); 176 | for ( i = 0; i < partsFoundCount; i++ ) { 177 | nameToParse = ( ' ' + nameToParse + ' ' ) 178 | .replace(partsFound[i], ' ').trim(); 179 | partsFound[i] = partsFound[i].slice(2).slice(0,-2); 180 | if ( partsFound[i].slice(-1) === ',' ) { 181 | partsFound[i] = partsFound[i].slice(0,-1); 182 | } 183 | } 184 | parsedName.nick = partsFound.join(', '); 185 | partsFound = []; 186 | } 187 | if ( !nameToParse.trim().length ) { 188 | parsedName = fixParsedNameCase(parsedName, fixCase); 189 | return partToReturn === 'all' ? parsedName : parsedName[partToReturn]; 190 | } 191 | 192 | // Split remaining nameToParse into parts, remove and store preceding commas 193 | for ( i = 0, n = nameToParse.split(' '), l = n.length; i < l; i++ ) { 194 | part = n[i]; 195 | comma = null; 196 | if ( part.slice(-1) === ',' ) { 197 | comma = ','; 198 | part = part.slice(0,-1); 199 | } 200 | nameParts.push(part); 201 | nameCommas.push(comma); 202 | } 203 | 204 | // Suffix: remove and store matching parts as suffixes 205 | for ( l = nameParts.length, i = l-1; i > 0; i-- ) { 206 | partToCheck = (nameParts[i].slice(-1) === '.' ? 207 | nameParts[i].slice(0,-1).toLowerCase() : nameParts[i].toLowerCase()); 208 | if ( 209 | suffixList.indexOf(partToCheck) > -1 || 210 | suffixList.indexOf(partToCheck+'.') > -1 211 | ) { 212 | partsFound = nameParts.splice(i,1).concat(partsFound); 213 | if ( nameCommas[i] === ',' ) { // Keep comma, either before or after 214 | nameCommas.splice(i+1,1); 215 | } else { 216 | nameCommas.splice(i,1); 217 | } 218 | } 219 | } 220 | partsFoundCount = partsFound.length; 221 | if ( partsFoundCount === 1 ) { 222 | parsedName.suffix = partsFound[0]; 223 | partsFound = []; 224 | } else if ( partsFoundCount > 1 ) { 225 | handleError(partsFoundCount + ' suffixes found'); 226 | parsedName.suffix = partsFound.join(', '); 227 | partsFound = []; 228 | } 229 | if ( !nameParts.length ) { 230 | parsedName = fixParsedNameCase(parsedName, fixCase); 231 | return partToReturn === 'all' ? parsedName : parsedName[partToReturn]; 232 | } 233 | 234 | // Title: remove and store matching parts as titles 235 | for( l = nameParts.length, i = l-1; i >= 0; i--) { 236 | partToCheck = (nameParts[i].slice(-1) === '.' ? 237 | nameParts[i].slice(0,-1).toLowerCase() : nameParts[i].toLowerCase()); 238 | if ( 239 | titleList.indexOf(partToCheck) > -1 || 240 | titleList.indexOf(partToCheck+'.') > -1 241 | ) { 242 | partsFound = nameParts.splice(i,1).concat(partsFound); 243 | if ( nameCommas[i] === ',' ) { // Keep comma, either before or after 244 | nameCommas.splice(i+1,1); 245 | } else { 246 | nameCommas.splice(i,1); 247 | } 248 | } 249 | } 250 | partsFoundCount = partsFound.length; 251 | if ( partsFoundCount === 1 ) { 252 | parsedName.title = partsFound[0]; 253 | partsFound = []; 254 | } else if ( partsFoundCount > 1 ) { 255 | handleError(partsFoundCount + ' titles found'); 256 | parsedName.title = partsFound.join(', '); 257 | partsFound = []; 258 | } 259 | if ( !nameParts.length ) { 260 | parsedName = fixParsedNameCase(parsedName, fixCase); 261 | return partToReturn === 'all' ? parsedName : parsedName[partToReturn]; 262 | } 263 | 264 | // Join name prefixes to following names 265 | if ( nameParts.length > 1 ) { 266 | for ( i = nameParts.length-2; i >= 0; i-- ) { 267 | if ( prefixList.indexOf(nameParts[i].toLowerCase()) > -1 ) { 268 | nameParts[i] = nameParts[i] + ' ' + nameParts[i+1]; 269 | nameParts.splice(i+1,1); 270 | nameCommas.splice(i+1,1); 271 | } 272 | } 273 | } 274 | 275 | // Join conjunctions to surrounding names 276 | if ( nameParts.length > 2 ) { 277 | for ( i = nameParts.length-3; i >= 0; i-- ) { 278 | if ( conjunctionList.indexOf(nameParts[i+1].toLowerCase()) > -1 ) { 279 | nameParts[i] = nameParts[i] + ' ' + nameParts[i+1] + ' ' + nameParts[i+2]; 280 | nameParts.splice(i+1,2); 281 | nameCommas.splice(i+1,2); 282 | i--; 283 | } 284 | } 285 | } 286 | 287 | // Suffix: remove and store items after extra commas as suffixes 288 | nameCommas.pop(); 289 | firstComma = nameCommas.indexOf(','); 290 | remainingCommas = nameCommas.filter(function(v) { return v !== null; }).length; 291 | if ( firstComma > 1 || remainingCommas > 1 ) { 292 | for ( i = nameParts.length-1; i >= 2; i-- ) { 293 | if ( nameCommas[i] === ',' ) { 294 | partsFound = nameParts.splice(i,1).concat(partsFound); 295 | nameCommas.splice(i,1); 296 | remainingCommas--; 297 | } else { 298 | break; 299 | } 300 | } 301 | } 302 | if ( partsFound.length ) { 303 | if ( parsedName.suffix ) { 304 | partsFound = [parsedName.suffix].concat(partsFound); 305 | } 306 | parsedName.suffix = partsFound.join(', '); 307 | partsFound = []; 308 | } 309 | 310 | // Last name: remove and store last name 311 | if ( remainingCommas > 0 ) { 312 | if ( remainingCommas > 1 ) { 313 | handleError( (remainingCommas-1) + ' extra commas found' ); 314 | } 315 | // Remove and store all parts before first comma as last name 316 | if ( nameCommas.indexOf(',') ) { 317 | parsedName.last = nameParts.splice(0,nameCommas.indexOf(',')).join(' '); 318 | nameCommas.splice(0,nameCommas.indexOf(',')); 319 | } 320 | } else { 321 | // Remove and store last part as last name 322 | parsedName.last = nameParts.pop(); 323 | } 324 | if ( !nameParts.length ) { 325 | parsedName = fixParsedNameCase(parsedName, fixCase); 326 | return partToReturn === 'all' ? parsedName : parsedName[partToReturn]; 327 | } 328 | 329 | // First name: remove and store first part as first name 330 | parsedName.first = nameParts.shift(); 331 | if ( !nameParts.length ) { 332 | parsedName = fixParsedNameCase(parsedName, fixCase); 333 | return partToReturn === 'all' ? parsedName : parsedName[partToReturn]; 334 | } 335 | 336 | // Middle name: store all remaining parts as middle name 337 | if ( nameParts.length > 2 ) { 338 | handleError(nameParts.length + ' middle names'); 339 | } 340 | parsedName.middle = nameParts.join(' '); 341 | 342 | parsedName = fixParsedNameCase(parsedName, fixCase); 343 | return partToReturn === 'all' ? parsedName : parsedName[partToReturn]; 344 | }; 345 | --------------------------------------------------------------------------------