parse static method
Parses raw text, Secure QR XML, or compressed binary payload from an Aadhaar card.
Implementation
static ScanResult parse(String rawData) {
if (rawData.contains('<?xml') ||
rawData.contains('<PrintLetterBarcodeData')) {
return _parseXmlQr(rawData);
}
if (isAadhaarPayload(rawData) && !rawData.contains('<?xml')) {
final secureResult = _parseSecureQr(rawData);
if (secureResult != null) return secureResult;
}
// Enhanced v3.0: Multi-candidate UID extraction with OCR error correction
final allDigitSequences = _extractAllDigitSequences(rawData);
final lines = rawData
.split(_lineSplitRegex)
.map((l) => l.trim())
.where((l) => l.isNotEmpty)
.toList();
// Strategy 1: Direct regex match for formatted Aadhaar number
String? bestUid;
bool isVerhoeffValid = false;
final uidMatch = _uidRegex.firstMatch(rawData);
if (uidMatch != null) {
final rawUid = uidMatch.group(0)!.replaceAll(' ', '');
if (validateAadhaarVerhoeff(rawUid)) {
bestUid = rawUid;
isVerhoeffValid = true;
} else {
// Try OCR digit correction on this candidate
final corrected = _tryOcrCorrection(rawUid);
if (corrected != null) {
bestUid = corrected;
isVerhoeffValid = true;
} else {
bestUid = rawUid; // Use as-is even if Verhoeff fails
}
}
}
// Strategy 2: Find any 12-digit sequence that passes Verhoeff
if (!isVerhoeffValid) {
for (final seq in allDigitSequences) {
if (seq.length >= 12) {
// Try each 12-digit window
for (int start = 0; start <= seq.length - 12; start++) {
final candidate = seq.substring(start, start + 12);
if (candidate.startsWith(RegExp(r'[2-9]'))) {
if (validateAadhaarVerhoeff(candidate)) {
bestUid = candidate;
isVerhoeffValid = true;
break;
}
// Try OCR correction
final corrected = _tryOcrCorrection(candidate);
if (corrected != null) {
bestUid = corrected;
isVerhoeffValid = true;
break;
}
}
}
if (isVerhoeffValid) break;
}
}
}
// Strategy 3: Concatenate nearby 4-digit groups (XXXX XXXX XXXX format)
if (!isVerhoeffValid) {
final fourDigitGroups = RegExp(
r'\b\d{4}\b',
).allMatches(rawData).map((m) => m.group(0)!).toList();
if (fourDigitGroups.length >= 3) {
for (int i = 0; i <= fourDigitGroups.length - 3; i++) {
final candidate =
fourDigitGroups[i] +
fourDigitGroups[i + 1] +
fourDigitGroups[i + 2];
if (candidate.startsWith(RegExp(r'[2-9]')) &&
candidate.length == 12) {
if (validateAadhaarVerhoeff(candidate)) {
bestUid = candidate;
isVerhoeffValid = true;
break;
}
final corrected = _tryOcrCorrection(candidate);
if (corrected != null) {
bestUid = corrected;
isVerhoeffValid = true;
break;
}
bestUid ??= candidate;
}
}
}
}
if (bestUid != null && bestUid.length >= 12) {
final cleanUid = bestUid.substring(0, 12);
final formattedUid =
'${cleanUid.substring(0, 4)} ${cleanUid.substring(4, 8)} ${cleanUid.substring(8, 12)}';
final Map<String, String> fields = {
'Card Type': 'Aadhaar Card (India)',
'Aadhaar Number': formattedUid,
'Verhoeff Checksum': isVerhoeffValid ? 'Valid ✓' : 'Invalid ✗',
'Verification Status': isVerhoeffValid
? 'UIDAI Verhoeff D10 Validated ✓'
: 'Checksum Mismatch ✗',
};
// Extract Name — look for lines with mostly uppercase alphabets
// (typically the cardholder name line on Aadhaar)
final nameCandidate = _extractName(lines);
if (nameCandidate != null) {
fields['Full Name'] = nameCandidate;
}
// Extract Date of Birth
for (final line in lines) {
if (_dobHeaderRegex.hasMatch(line)) {
final dobMatch = _dobMatchRegex.firstMatch(line);
if (dobMatch != null) {
fields['Date of Birth'] = dobMatch.group(0)!;
final yr = int.tryParse(dobMatch.group(0)!.substring(6));
if (yr != null) {
fields['Calculated Age'] = '${DateTime.now().year - yr} years';
}
}
} else if (_yobHeaderRegex.hasMatch(line)) {
final yobMatch = _yobMatchRegex.firstMatch(line);
if (yobMatch != null) {
fields['Year of Birth'] = yobMatch.group(0)!;
final yr = int.tryParse(yobMatch.group(0)!);
if (yr != null) {
fields['Approximate Age'] = '${DateTime.now().year - yr} years';
}
}
}
}
// Try extracting DOB from raw text if not found via headers
if (!fields.containsKey('Date of Birth') &&
!fields.containsKey('Year of Birth')) {
final dobAnywhere = RegExp(
r'\b\d{2}[/\-.]\d{2}[/\-.]\d{4}\b',
).firstMatch(rawData);
if (dobAnywhere != null) {
fields['Date of Birth'] = dobAnywhere.group(0)!;
final parts = dobAnywhere.group(0)!.split(RegExp(r'[/\-.]'));
if (parts.length == 3) {
final yr = int.tryParse(parts[2]);
if (yr != null && yr > 1900 && yr < DateTime.now().year) {
fields['Calculated Age'] = '${DateTime.now().year - yr} years';
}
}
}
}
// Extract Gender
for (final line in lines) {
if (_genderRegex.hasMatch(line)) {
final match = _genderRegex.firstMatch(line);
if (match != null) {
fields['Gender'] = match.group(0)!;
}
break;
}
}
// Also check for single M/F near gender keywords
if (!fields.containsKey('Gender')) {
final genderShort = RegExp(
r'(?:Gender|Sex)\s*[:\-]?\s*([MF])\b',
caseSensitive: false,
).firstMatch(rawData);
if (genderShort != null) {
final g = genderShort.group(1)!.toUpperCase();
fields['Gender'] = g == 'M' ? 'Male' : 'Female';
}
}
// Extract Pincode
final pincodeMatch = _pincodeRegex.firstMatch(rawData);
if (pincodeMatch != null) {
fields['Pincode'] = pincodeMatch.group(0)!;
}
// Extract VID (Virtual ID) — 16-digit number
final vidMatch = RegExp(
r'\b\d{4}\s?\d{4}\s?\d{4}\s?\d{4}\b',
).firstMatch(rawData);
if (vidMatch != null) {
final vidClean = vidMatch.group(0)!.replaceAll(' ', '');
if (vidClean.length == 16 && vidClean != cleanUid) {
fields['VID'] =
'${vidClean.substring(0, 4)} ${vidClean.substring(4, 8)} ${vidClean.substring(8, 12)} ${vidClean.substring(12, 16)}';
}
}
// Extract Address lines (lines below gender/DOB, above Aadhaar number)
final addressLines = _extractAddressLines(lines, cleanUid);
if (addressLines.isNotEmpty) {
fields['Address'] = addressLines.join(', ');
}
final verifications = {
'verhoeffChecksum': isVerhoeffValid,
'uidSyntaxValid': true,
'pincodeValid': fields.containsKey('Pincode'),
'nameExtracted': fields.containsKey('Full Name'),
'dobExtracted':
fields.containsKey('Date of Birth') ||
fields.containsKey('Year of Birth'),
};
return ScanResult(
mode: ScanMode.aadhaar,
rawValue: rawData,
isValid: isVerhoeffValid,
confidence: isVerhoeffValid ? 0.99 : 0.70,
fields: fields,
verifications: verifications,
);
}
return ScanResult(
mode: ScanMode.aadhaar,
rawValue: rawData,
isValid: false,
confidence: 0.3,
fields: {
'Card Type': 'Aadhaar (Unparsed)',
'Raw Input': rawData,
'Detection Note':
'No valid 12-digit Aadhaar number found. Ensure card is well-lit and in focus.',
},
verifications: {'verhoeffChecksum': false, 'uidSyntaxValid': false},
);
}