UNPKG

id-scanner-lib

Version:

Browser-based ID card, QR code, and face recognition scanner with liveness detection

152 lines (139 loc) 6.57 kB
import { IDCardType, IDCardInfo } from './types'; /** * 格式化日期字符串为标准格式 (YYYY-MM-DD) */ function formatDateString(dateStr: string): string { const dateMatch = dateStr.match(/(\d{4})[-\.\u5e74\s]*(\d{1,2})[-\.\u6708\s]*(\d{1,2})[日]*/); if (dateMatch) { const year = dateMatch[1]; const month = dateMatch[2].padStart(2, "0"); const day = dateMatch[3].padStart(2, "0"); return `${year}-${month}-${day}`; } if (/^\d{8}$/.test(dateStr)) { const year = dateStr.substring(0, 4); const month = dateStr.substring(4, 6); const day = dateStr.substring(6, 8); return `${year}-${month}-${day}`; } return dateStr; } /** * 验证身份证号是否符合规则 */ function validateIDNumber(idNumber: string): boolean { if (!idNumber || idNumber.length !== 18) return false; const pattern = /^\d{17}[\dX]$/; if (!pattern.test(idNumber)) return false; const year = parseInt(idNumber.substr(6, 4)); const month = parseInt(idNumber.substr(10, 2)); const day = parseInt(idNumber.substr(12, 2)); if (month < 1 || month > 12 || day < 1 || day > 31) return false; return true; } /** * IDCardTextParser - 统一解析身份证OCR文本 * 提取 ocr-processor.ts 和 ocr-worker.ts 中的解析逻辑 */ export class IDCardTextParser { /** * 解析身份证文本 * @param text OCR识别的原始文本 * @returns 解析后的身份证信息 */ static parse(text: string): IDCardInfo { const info: IDCardInfo = {}; const processedText = text.replace(/\s+/g, " ").trim(); const lines = processedText.split("\n").filter((line) => line.trim()); // 1. 解析身份证号码 const idNumberRegex = /(\d{17}[\dX])/; const idNumberWithPrefixRegex = /公民身份号码[\s\:]*(\d{17}[\dX])/; const basicMatch = processedText.match(idNumberRegex); const prefixMatch = processedText.match(idNumberWithPrefixRegex); if (prefixMatch && prefixMatch[1]) { info.idNumber = prefixMatch[1]; } else if (basicMatch && basicMatch[1]) { info.idNumber = basicMatch[1]; } // 2. 解析姓名 const nameWithLabelRegex = /姓名[\s\:]*([一-龥]{2,4})/; const nameMatch = processedText.match(nameWithLabelRegex); if (nameMatch && nameMatch[1]) { info.name = nameMatch[1].trim(); } else { for (const line of lines) { if (line.length >= 2 && line.length <= 5 && /^[一-龥]+$/.test(line) && !/性别|民族|住址|公民|签发|有效/.test(line)) { info.name = line.trim(); break; } } } // 3. 解析性别和民族 const genderAndNationalityRegex = /性别[\s\:]*([男女])[\s ]*民族[\s\:]*([一-龥]+族)/; const genderOnlyRegex = /性别[\s\:]*([男女])/; const nationalityOnlyRegex = /民族[\s\:]*([一-龥]+族)/; const genderNationalityMatch = processedText.match(genderAndNationalityRegex); const genderOnlyMatch = processedText.match(genderOnlyRegex); const nationalityOnlyMatch = processedText.match(nationalityOnlyRegex); if (genderNationalityMatch) { info.gender = genderNationalityMatch[1]; info.ethnicity = genderNationalityMatch[2]; } else { if (genderOnlyMatch) info.gender = genderOnlyMatch[1]; if (nationalityOnlyMatch) info.ethnicity = nationalityOnlyMatch[1]; } // 4. 判断身份证类型 if (processedText.includes('出生') || processedText.includes('公民身份号码')) { info.type = IDCardType.FRONT; } else if (processedText.includes('签发机关') || processedText.includes('有效期')) { info.type = IDCardType.BACK; } // 5. 解析出生日期 const birthDateRegex1 = /出生[\s\:]*(\d{4})年(\d{1,2})月(\d{1,2})[日号]/; const birthDateRegex2 = /出生[\s\:]*(\d{4})[-\/\.](\d{1,2})[-\/\.](\d{1,2})/; const birthDateRegex3 = /出生日期[\s\:]*(\d{4})[-\/\.\u5e74](\d{1,2})[-\/\.\u6708](\d{1,2})[日号]?/; const birthDateMatch = processedText.match(birthDateRegex1) || processedText.match(birthDateRegex2) || processedText.match(birthDateRegex3); if (!birthDateMatch && info.idNumber && info.idNumber.length === 18) { const year = info.idNumber.substring(6, 10); const month = info.idNumber.substring(10, 12); const day = info.idNumber.substring(12, 14); info.birthDate = `${year}-${month}-${day}`; } else if (birthDateMatch) { const year = birthDateMatch[1]; const month = birthDateMatch[2].padStart(2, "0"); const day = birthDateMatch[3].padStart(2, "0"); info.birthDate = `${year}-${month}-${day}`; } // 6. 解析地址 const addressRegex1 = /住址[\s\:]*([\s\S]*?)(?=公民身份|出生|性别|签发)/; const addressRegex2 = /住址[\s\:]*([一-龥a-zA-Z0-9\s\.\-]+)/; const addressMatch = processedText.match(addressRegex1) || processedText.match(addressRegex2); if (addressMatch && addressMatch[1]) { info.address = addressMatch[1].replace(/\s+/g, "").replace(/\n/g, "").trim(); if (info.address.length > 70) info.address = info.address.substring(0, 70); if (!/[一-龥]/.test(info.address)) info.address = ''; } // 7. 解析签发机关 const authorityRegex1 = /签发机关[\s\:]*([\s\S]*?)(?=有效|公民|出生|\d{8}|$)/; const authorityRegex2 = /签发机关[\s\:]*([一-龥\s]+)/; const authorityMatch = processedText.match(authorityRegex1) || processedText.match(authorityRegex2); if (authorityMatch && authorityMatch[1]) { info.issueAuthority = authorityMatch[1].replace(/\s+/g, "").replace(/\n/g, "").trim(); } // 8. 解析有效期限 const validPeriodRegex1 = /有效期限[\s\:]*(\d{4}[-\.\u5e74\s]\d{1,2}[-\.\u6708\s]\d{1,2}[日\s]*)[-\s]*(至|-)[-\s]*(\d{4}[-\.\u5e74\s]\d{1,2}[-\.\u6708\s]\d{1,2}[日]*|[永久长期]*)/; const validPeriodRegex2 = /有效期限[\s\:]*(\d{8})[-\s]*(至|-)[-\s]*(\d{8}|[永久长期]*)/; const validPeriodMatch = processedText.match(validPeriodRegex1) || processedText.match(validPeriodRegex2); if (validPeriodMatch && validPeriodMatch[1] && validPeriodMatch[3]) { const startDate = formatDateString(validPeriodMatch[1]); const endDate = /\d/.test(validPeriodMatch[3]) ? formatDateString(validPeriodMatch[3]) : '长期有效'; info.validFrom = startDate; info.validTo = endDate; info.validPeriod = `${startDate}-${endDate}`; } else if (validPeriodMatch) { info.validPeriod = validPeriodMatch[0].replace("有效期限", "").trim(); } return info; } }