/ src / lib / utils / orcids / is-valid-orcid-id.ts
is-valid-orcid-id.ts
 1  /**
 2   * Validates an ORCID iD.
 3   *
 4   * An ORCID iD is a 16-character string that follows a specific structure:
 5   * - It consists of 15 digits followed by a check digit (0-9 or 'X').
 6   * - It is often formatted with hyphens, e.g., "0000-0002-1825-0097".
 7   * - The validation uses the ISO 7064 11,2 checksum algorithm.
 8   *
 9   * @param {string} orcid The ORCID iD string to validate.
10   * @returns {boolean} True if the ORCID iD is valid, false otherwise.
11   */
12  export default function isValidOrcidId(orcidId: string): boolean {
13    if (typeof orcidId !== 'string') {
14      return false;
15    }
16  
17    // Remove hyphens and whitespace to get the base 16 characters.
18    const baseStr: string = orcidId.replace(/[-\s]/g, '');
19  
20    // An ORCID must be 16 characters long and match the pattern:
21    // 15 digits followed by a final character that is a digit or 'X'.
22    const orcidPattern: RegExp = /^\d{15}[\dX]$/;
23    if (!orcidPattern.test(baseStr.toUpperCase())) {
24      return false;
25    }
26  
27    // --- Checksum Calculation (ISO 7064 11,2) ---
28  
29    let total: number = 0;
30    // Iterate over the first 15 digits of the ORCID.
31    for (let i = 0; i < 15; i++) {
32      const digit: number = parseInt(baseStr[i], 10);
33      total = (total + digit) * 2;
34    }
35  
36    // Calculate the remainder when divided by 11.
37    const remainder: number = total % 11;
38    // Subtract the remainder from 12.
39    const result: number = (12 - remainder) % 11;
40  
41    // Determine the correct check digit from the result.
42    // If the result is 10, the check digit is 'X'. Otherwise, it's the digit itself.
43    const calculatedCheckDigit: string = result === 10 ? 'X' : String(result);
44  
45    // Get the actual check digit from the input string.
46    const actualCheckDigit: string = baseStr.charAt(15).toUpperCase();
47  
48    // Compare the calculated check digit with the actual one.
49    return calculatedCheckDigit === actualCheckDigit;
50  }