الاسم هو أقل حقول منطقة القراءة الآلية (MRZ، اختصار Machine Readable Zone) قابلية للتنبؤ. التواريخ ستة أرقام، ورقم الوثيقة تسعة محارف مع رقم تحقق، والرموز تأتي من قائمة ثابتة. أما الاسم فهو ما يُدعى به الشخص، مضغوطًا في عدد ثابت من الحروف الكبيرة، بلا مسافات ولا علامات تشكيل ولا علامات ترقيم، ودون رقم تحقق يخبرك متى قُرئ محرف قراءةً خاطئة.
يشرح هذا المقال كيف تكتب المنطقة الاسم، وكيف تفصله من جديد إلى اسم العائلة والأسماء الأولى، وما الذي يفعله النقل الحرفي به، وكيف تعرف أنه ربما اقتُطع، وكيف تقارنه بالاسم المطبوع في الصفحة. كل الأسماء أدناه مختلَقة أو هي عيّنة ICAO نفسها، Anna Maria Eriksson؛ لا يظهر أي شخص حقيقي.
هذه مدونة doc.cheap، وهي واجهة API للتعرّف على جوازات السفر وبطاقات الهوية. لا شيء مما يلي يحتاج إليها؛ فالشيفرة بلا اعتماديات.
أين يقع الاسم
يعطي المستند Doc 9303 الصادر عن ICAO، وهو معيار وثائق السفر المقروءة آليًا، الاسمَ حقلًا بعرض ثابت في كل تنسيق من تنسيقاته الثلاثة. الترقيم هنا يبدأ من 1، كما يعدّ المعيار:
| التنسيق | موضع الاسم | العرض |
|---|---|---|
| TD3 (جوازات السفر)، 2 × 44 | السطر 1، المواضع 6–44 | 39 محرفًا |
| TD2، 2 × 36 | السطر 1، المواضع 6–36 | 31 محرفًا |
| TD1 (بطاقات الهوية)، 3 × 30 | السطر 3، السطر كله | 30 محرفًا |
المحارف الخمسة الأولى من السطر 1 في TD3 أو TD2 هي رمز الوثيقة والدولة المُصدِرة؛ ويشغل الاسم كل ما بعدها. يعطي TD1 الاسم سطرًا خاصًا به، ومع ذلك يبقى الأضيق بين الثلاثة. وتشرح صفحة تنسيقات MRZ كل تنسيق حقلًا حقلًا.
في عيّنة TD3 يكون السطر 1 هو P<UTOERIKSSON<<ANNA<MARIA<<<<<<<<<<<<<<<<<<<. احذف P<UTO فيصبح حقل الاسم ERIKSSON<<ANNA<MARIA تتبعه محارف حشو حتى 39 محرفًا.
القواعد: << و< والحشو
أبجدية المنطقة هي A–Z و0–9 ومحرف الحشو <. ويضيف الاسم فوقها ثلاثة أعراف:
- المُعرِّف الأساسي يأتي أولًا. وهو عادةً اسم العائلة.
- محرفا حشو،
<<، يُنهيان المُعرِّف الأساسي. وما يليهما هو المُعرِّف الثانوي، وهو عادةً الأسماء الأولى. - محرف حشو واحد،
<، يفصل بين الكلمات داخل كل جزء.ANNA<MARIAاسمان أولان؛ وVAN<DER<BERGاسم عائلة من ثلاث كلمات.
ما يتبقى من الحقل بعد الاسم يُملأ بـ<. لذا فإن قراءة الاسم من جديد ثلاث خطوات: احذف محارف الحشو في النهاية، وافصل عند أول <<، وحوّل كل < منفرد متبقٍّ إلى مسافة.
شكلان يبدوان غريبين وليسا خطأً. الشخص الذي له اسم واحد لا يملك إلا مُعرِّفًا أساسيًا، فيكون الحقل هو الاسم ثم محارف الحشو، دون << قبلها: OKONKWO<<<<…. والحقل المكتوب بالفاصل ولا شيء بعده، NAKAMURA<<<<…، يُقرأ بالطريقة نفسها تمامًا بعد حذف محارف الحشو في النهاية. وفي الحالتين يكون المُعرِّف الثانوي فارغًا، لا مفقودًا.
ما لا يبقى هو علامات الترقيم والمسافة نفسها. لا يمكن كتابة الشرطة أو الفاصلة العليا أو المسافة في المنطقة، لذا يصل Smith-Jones المطبوع بصيغة SMITH<JONES، ولا يمكن تمييزه عن اسم عائلة من كلمتين. إذا احتجت إلى علامات الترقيم، فهي في الصفحة المطبوعة، لا في المنطقة.
النقل الحرفي: من أي نظام كتابة إلى 26 حرفًا
يجب أن ينتهي كل اسم داخل A–Z. بالنسبة إلى اسم مطبوع بحروف لاتينية تحمل علامات تشكيل، يعني ذلك ربط كل حرف مُشكَّل بحروف بسيطة. وبالنسبة إلى اسم باليونانية أو العربية أو السيريلية أو الحروف الصينية (Han) أو أي نظام كتابة آخر، يعني ذلك كتابة الاسم كله بحروف لاتينية.
يتضمن الجزء 3 من Doc 9303، وهو الجزء المشترك بين كل التنسيقات، نقلًا حرفيًا موصى به للحالتين. غير أن القرار للدولة المُصدِرة، ولا تختار الدول كلها بالطريقة نفسها. وتهمّ الشيفرةَ ثلاث نتائج:
- قد يكون للاسم المطبوع أكثر من تهجئة واحدة في المنطقة. قد يظهر
MÜLLERالمطبوع في المنطقة بصيغةMUELLERأوMULLER، بحسب قواعد الجهة المُصدِرة. والشيفرة التي تتوقع ربطًا ثابتًا واحدًا ستعدّ منطقةً صحيحة خاطئة. - الربط لا يُعكس. قد يأتي
MULLERفي المنطقة منMULLERأوMÜLLERأوMÚLLERفي الصفحة. تخبرك المنطقة بالحروف التي تحملها، لا بالاسم الذي يكتبه الشخص. - قد يختلف الاسم اللاتيني في الصفحة عن المنطقة. كثيرًا ما تطبع الوثيقة المطبوعة بنظام كتابة وطني نسخة لاتينية أيضًا، وتلك التهجئة اللاتينية المطبوعة ليست دائمًا مطابقة حرفًا بحرف لما في المنطقة. تعامل مع الاختلاف على أنه شيء يستحق النظر، لا على أنه دليل على قراءة خاطئة.
القاعدة العملية: قارن اسم المنطقة بالاسم المطبوع بعد اختزال كليهما إلى حروف مجردة، واسمح بالتهجئات التي تستخدمها الجهات المُصدِرة لديك فعلًا، واحتفظ بالتهجئة المطبوعة لكل ما يقرؤه إنسان.
الاقتطاع: حين لا يتسع الحقل للاسم
الاسم الطويل لا ينتقل إلى سطر آخر. بل يُختصر ليتسع له الحقل، ويبقى الاسم الكامل في الصفحة المطبوعة. يحدد المعيار قواعد لكيفية اختصار الاسم، والاسم المختصر يمتد عادةً حتى آخر محرف في حقله.
وهذا يعطي المحلل الإشارة الوحيدة المتاحة له: إذا كان آخر محرف في حقل الاسم حرفًا لا <، فربما اقتُطع الاسم. إنها «ربما» لا «بالتأكيد»: فالاسم الذي يساوي طوله طول الحقل تمامًا ينتهي أيضًا بحرف. لكن الحقل الذي ينتهي بمحارف حشو لم يُقتطع قطعًا.
العرض مهم. خذ حاملًا مختلَقًا، Konstantin Friedrich Hartmann Schoenebeck. في حقل TD3 بعرض 39 محرفًا:
HARTMANN<SCHOENEBECK<<KONSTANTIN<FRIEDR
آخر محرف هو R، وهو حرف، لذا ربما اقتُطع الاسم، وقد اقتُطع فعلًا: فقد FRIEDRICH ثلاثة حروف. والاسم نفسه حين يُقتطع بعرض TD1 البالغ 30 لا يحتفظ إلا بـHARTMANN<SCHOENEBECK<<KONSTANT. والنظام الذي يخزّن اسم المنطقة على أنه اسم الحامل يخزّن KONSTANT اسمًا أول.
تنتج عن ذلك عادتان. لا تستخدم اسم المنطقة أبدًا اسمًا للعرض حين يكون الحقل ممتلئًا؛ استخدم الاسم المطبوع. وحين تطابق اسمًا مطبوعًا كاملًا مع اسم المنطقة، فإن الاسم المطبوع الذي يبدأ باسم المنطقة يُعدّ مطابقًا إذا كان الحقل ممتلئًا.
قد تختصر الجهة المُصدِرة الاسم أيضًا بطرق غير قطع نهايته، كأن تختزل اسمًا أول ثانيًا إلى حرفه الأول. واختبار البادئة يعدّ اسمًا كهذا غير مطابق. أحِل هذه الحالة إلى إنسان بدلًا من رفض الوثيقة.
لا يغطي أي رقم تحقق الاسم
تغطي أرقام التحقق رقم الوثيقة، والتاريخين، والرقم الشخصي الاختياري في TD3، ورقمًا مركّبًا يشمل معظم السطر السفلي. لا يغطي أيٌّ منها الاسم. في TD3 وTD2 يقع الاسم في السطر 1، الذي لا يمسّه أي رقم إطلاقًا؛ وفي TD1 يقع في السطر 3، والرقم المركّب يتوقف عند السطر 2.
لذا فإن القراءة الخاطئة في الاسم صامتة. حرف M يُقرأ N، أو محرف حشو يُقرأ حرفًا، يعطي مع ذلك منطقة تتوازن حساباتها. التحقق المتقاطع الوحيد للاسم هو الصفحة المطبوعة: اقرأ الاثنين وقارن بينهما، وهذا ما وُضعت له دالة المقارنة أدناه. ويشرح مقال كيف تعمل أرقام التحقق في MRZ الحساب للحقول المغطاة.
فصل الحقل بلغة Python
دون أي اعتماديات. تأخذ الدالة حقل الاسم وحده، بعد اقتطاعه مسبقًا من سطره (line1[5:44] في TD3، وline1[5:36] في TD2، وline3 في TD1).
import re
def words(part):
return " ".join(word for word in part.split("<") if word)
def read_mrz_name(field):
"""Split an MRZ name field into (primary, secondary, may_be_truncated)."""
if not re.fullmatch(r"[A-Z<]+", field):
raise ValueError("a name field holds only A-Z and the filler <")
may_be_truncated = not field.endswith("<")
body = field.rstrip("<")
primary, _separator, secondary = body.partition("<<")
return words(primary), words(secondary), may_be_truncated
تفصل partition عند أول << فقط، وتُرجع جزءًا ثانويًا فارغًا حين لا يوجد فاصل، وهذه حالة الاسم الواحد. ويرفض النمط الأرقام عن قصد: فالرقم 0 في حقل الاسم هو O قُرئ خطأً، وينبغي أن يصل إلى إنسان بدلًا من أن يُطبع اسمًا.
الفصل نفسه بلغة JavaScript
export function readMrzName(field) {
if (!/^[A-Z<]+$/.test(field)) throw new Error("a name field holds only A-Z and the filler <");
const mayBeTruncated = !field.endsWith("<");
const body = field.replace(/<+$/, "");
const at = body.indexOf("<<");
const words = (part) => part.split("<").filter(Boolean).join(" ");
if (at < 0) return { primary: words(body), secondary: "", mayBeTruncated };
return { primary: words(body.slice(0, at)), secondary: words(body.slice(at + 2)), mayBeTruncated };
}
مقارنة المنطقة بالاسم المطبوع
يأتي الاسم المطبوع من المنطقة المرئية، وهي جزء الصفحة الذي يقرؤه الإنسان، بعلامات تشكيله وترقيمه. تختزله الدالة أدناه إلى حروف كبيرة مجردة وتقارنه بجزء واحد من اسم المنطقة. وتُرجع إحدى ثلاث كلمات: match أو mismatch أو unknown حين يحتوي الاسم المطبوع حرفًا لا يُختزل وحده إلى A–Z، مثل Ø أو Æ أو Ł.
import re
import unicodedata
# The spellings to try besides plain removal of the accent. These three are
# an example; put in the ones your issuers use.
EXPANSIONS = {"Ä": "AE", "Ö": "OE", "Ü": "UE"}
def compare_name(zone_part, printed_part, truncated=False, expansions=EXPANSIONS):
"""'match', 'mismatch' or 'unknown' for one name part: zone against printed."""
zone = zone_part.replace(" ", "")
upper = unicodedata.normalize("NFC", printed_part).upper()
candidates = {upper, "".join(expansions.get(c, c) for c in upper)}
verdict = "unknown"
for candidate in candidates:
letters = "".join(
c for c in unicodedata.normalize("NFKD", candidate)
if c.isalpha() and not unicodedata.combining(c)
)
if not re.fullmatch(r"[A-Z]*", letters):
continue # a letter with no plain A-Z form: leave it to a person
if letters == zone or (truncated and letters.startswith(zone)):
return "match"
verdict = "mismatch"
return verdict
يفكك NFKD الحرف المُشكَّل إلى الحرف البسيط وعلامته، وتُحذف العلامة. وتُحذف كذلك المسافات والشرطات والفواصل العليا، لأن المقارنة على الحروف وحدها: يصبح Smith-Jones وSMITH JONES كلاهما SMITHJONES. وبالنسبة إلى اسم مطبوع بنظام كتابة غير لاتيني فقط، قارن المنطقة بالنسخة اللاتينية التي تطبعها الصفحة، لا بنسخة الكتابة الوطنية؛ فالكتابة الوطنية ستُرجع هنا unknown في كل مرة.
متجهات الاختبار
كل الأسماء مختلَقة باستثناء عيّنة ICAO. في الجدول الأول يُحشى كل حقل بـ< حتى عرض TD3 البالغ 39 (في Python: name.ljust(39, "<"))، باستثناء الصف الأخير، الذي يبلغ طوله 39 محرفًا تمامًا من الأصل.
| الحقل قبل الحشو | الأساسي | الثانوي | ربما مقتطع |
|---|---|---|---|
ERIKSSON<<ANNA<MARIA |
ERIKSSON | ANNA MARIA | لا |
VAN<DER<BERG<<PIETER |
VAN DER BERG | PIETER | لا |
SMITH<JONES<<MARY |
SMITH JONES | MARY | لا |
OKONKWO |
OKONKWO | (فارغ) | لا |
NAKAMURA<< |
NAKAMURA | (فارغ) | لا |
HARTMANN<SCHOENEBECK<<KONSTANTIN<FRIEDR |
HARTMANN SCHOENEBECK | KONSTANTIN FRIEDR | نعم |
ولـcompare_name:
| جزء المنطقة | الجزء المطبوع | مقتطع | المتوقع |
|---|---|---|---|
ANNA MARIA |
Anna Maria |
لا | match |
SMITH JONES |
Smith-Jones |
لا | match |
MULLER |
Müller |
لا | match |
MUELLER |
Müller |
لا | match (عبر التوسيع) |
MILLER |
Müller |
لا | mismatch |
KONSTANTIN FRIEDR |
Konstantin Friedrich |
نعم | match |
KONSTANTIN FRIEDR |
Konstantin Friedrich |
لا | mismatch |
SORENSEN |
Sørensen |
لا | unknown |
الصف الأخير مقصود. فتحوُّل Ø إلى O أو إلى OE اختيار الجهة المُصدِرة، والدالة التي تخمّن ستخطئ بالنسبة إلى أحدٍ ما؛ أضف بدلًا من ذلك الصيغة التي تستخدمها الجهات المُصدِرة لديك إلى التوسيعات.
ما يفعله محلل MRZ لدينا
يقرأ محلل MRZ في هذا الموقع الاسم بالطريقة نفسها: يحذف محارف الحشو في النهاية، ويفصل عند أول <<، ويحوّل محارف الحشو المنفردة إلى مسافات؛ والحقل الخالي من << يُقرأ على أنه اسم عائلة وحده. وحين يمتد الاسم حتى آخر محرف في حقله، يعرض تحذيرًا بأن الاسم ربما اقتُطع ليتسع له الحقل، مع المواضع التي يشغلها. يعمل المحلل في المتصفح، فيمكنك لصق منطقة اصطناعية ومقارنة قراءته بشيفرتك الخاصة.
أين يندرج هذا في مسار العمل
تقرأ خدمة التعرّف المستضافة المنطقة والصفحة المطبوعة كلتيهما، لذا فالسؤال هو أي اسم تسلّمك. في استجابة doc.cheap تحمل الحقول holder.surname وholder.given_names وholder.full_name القراءة اللاتينية المدمجة. أما الاسم المطبوع بنظام كتابة وطني فهو مدخل منفصل في fields: لكل قراءة مدخلها الخاص، وللقراءة اللاتينية الافتراضية id ينتهي بـ@0، وتذكر قراءة الكتابة الوطنية قيمة language الخاصة بها، مثل Greek. والمنطقة نفسها موجودة في mrz.lines كما قُرئت تمامًا، لكي تقتطع حقل الاسم وتشغّل عليه الدالتين أعلاه. ويوضح دليل أنظمة الكتابة غير اللاتينية كيف تجمع القراءات وتخزّن التهجئتين معًا. وإن كنت لا تزال تختار خدمة، فإن مقارنة واجهات API للتعرّف على جوازات السفر تضع الأسعار المنشورة جنبًا إلى جنب.
إذا وجدت اسمًا تقرؤه الشيفرة أعلاه قراءةً خاطئة، فاكتب إلى admin@doc.cheap.
سؤال لك: أي نقل حرفي فاجأك أكثر من غيره حين قارنت منطقةً بالاسم المطبوع؟ أخبرنا في التعليقات أدناه.