Regex Polish licence plate
A Polish car plate starts with 2 or 3 letters for voivodeship and powiat ('DW', 'DBL'), then a vehicle part whose shape depends on the prefix length. The 8 November 2024 regulation (Dz.U. 2024 item 1709, § 31) excludes letters B, D, I, O and Z from that part and forbids all-zero numbers ('00000', '0A345'). Real attempts stick to wide classes: 'DW 1B345' and 'DW 00000' pass almost everywhere, and the Ruby gem excludes I, O and U instead of B, D, I, O and Z. Here's a pattern following the regulation's tables, compared against those attempts.
/^(?:[A-PR-Z]{2} (?:(?!0{5})[0-9]{5}|(?!0{4})[0-9]{4}[ACE-HJ-NPR-Y]|(?!0{3})[0-9]{3}[ACE-HJ-NPR-Y]{2}|[1-9][ACE-HJ-NPR-Y](?!0{3})[0-9]{3}|[1-9][ACE-HJ-NPR-Y]{2}(?!00)[0-9]{2})|[A-PR-Z]{3} (?:[ACE-HJ-NPR-Y](?!0{3})[0-9]{3}|(?!00)[0-9]{2}[ACE-HJ-NPR-Y]{2}|[1-9][ACE-HJ-NPR-Y](?!00)[0-9]{2}|(?!00)[0-9]{2}[ACE-HJ-NPR-Y][1-9]|[1-9][ACE-HJ-NPR-Y]{2}[1-9]|[ACE-HJ-NPR-Y]{2}(?!00)[0-9]{2}|(?!0{5})[0-9]{5}|(?!0{4})[0-9]{4}[ACE-HJ-NPR-Y]|(?!0{3})[0-9]{3}[ACE-HJ-NPR-Y]{2}))$/passes 33/33 cases
Comparison of existing patterns
| Source | pattern | Score on the corpus | ReDoS |
|---|---|---|---|
| ariejan/license_plate_validator (gem Ruby, 38 regex réunies par |) — la classe « [A-HJ-NP-TV-Z] » exclut I, O et U au lieu de B, D, I, O, Z : « DW 1B345 » et « DW 1234Z » passent ; zéros initiaux acceptés ; préfixe [A-Z]{2} accepte Q | ^([A-Z]{2})\s([0-9]{5})$|^([A-Z]{2})\s([0-9]{4}[A-HJ-NP-TV-Z])$|^([A-Z]{2})\s([0-9]{3}[A-HJ-NP-TV-Z]{2})$|^([A-Z]{2})\s([0-9][A-HJ-NP-TV-Z][0-9]{3})$|^([A-Z]{2})\s([0-9][A-HJ-NP-TV-Z]{2}[0-9]{2})$|^([A-Z]{3})\s([A-HJ-NP-TV-Z][0-9]{3})$|^([A-Z]{3})\s([0-9]{2}[A-HJ-NP-TV-Z]{2})$|^([A-Z]{3})\s([0-9][A-HJ-NP-TV-Z][0-9]{2})$|^([A-Z]{3})\s([0-9]{2}[A-HJ-NP-TV-Z][0-9])$|^([A-Z]{3})\s([0-9][A-HJ-NP-TV-Z]{2}[0-9])$|^([A-Z]{3})\s([A-HJ-NP-TV-Z]{2}[0-9]{2})$|^([A-Z]{3})\s([0-9]{5})$|^([A-Z]{3})\s([0-9]{4}[A-HJ-NP-TV-Z])$|^([A-Z]{3})\s([0-9]{3}[A-HJ-NP-TV-Z]{2})$|^([A-Z]{2})\s([0-9]{4})$|^([A-Z]{2})\s([0-9]{3}[A-HJ-NP-TV-Z])$|^([A-Z]{2})\s([0-9][A-HJ-NP-TV-Z][0-9]{2})$|^([A-Z]{2})\s([0-9]{2}[A-HJ-NP-TV-Z][0-9])$|^([A-Z]{2})\s([0-9]{2}[A-HJ-NP-TV-Z]{2})$|^([A-Z]{2})\s([A-HJ-NP-TV-Z]{2}[0-9]{2})$|^([A-Z])\s([0-9]{3})$|^([A-Z])\s([0-9]{2}[A-HJ-NP-TV-Z])$|^([A-Z])\s([0-9][A-HJ-NP-TV-Z][0-9])$|^([A-Z])\s([A-HJ-NP-TV-Z][0-9]{2})$|^([A-Z])\s([0-9][A-HJ-NP-TV-Z]{2})$|^([A-Z])\s([A-HJ-NP-TV-Z]{2}[0-9])$|^([A-Z])\s([A-HJ-NP-TV-Z][0-9][A-HJ-NP-TV-Z])$|^([A-Z]{2})\s([0-9]{2}[A-HJ-NP-TV-Z])$|^([A-Z]{2})\s([0-9]{3})$|^([A-Z]{3})\s([0-9][A-HJ-NP-TV-Z])$|^([A-Z]{3})\s([0-9]{2})$|^([A-Z]{3})\s([A-HJ-NP-TV-Z][0-9])$|^([A-Z][0-9])\s([0-9]{4})$|^([A-Z][0-9])\s([0-9]{3}[A-HJ-NP-TV-Z])$|^([A-Z][0-9])\s([0-9]{3})\sB$|^([A-Z][0-9])\s([A-Z][A-Z]{2,4})$|^([A-Z][0-9])\s([A-Z][A-Z]{1,3}[0-9]{1,2})$|^([A-Z][0-9]{2})\s([0-9]{2}P[0-9]{2})$ | 25 / 33 | safe |
| @randock/license-plate-validator (npm, deux regex réunies par | ; l'entrée est d'abord normalisée) — exclut bien B, D, I, O, Z, mais « {2,5} » laisse passer n'importe quelle longueur et mélange : « DW 123456 », « DW 12AC3 », « DW 00000 » passent | ^([A-PR-Z]{2})([0-9][ACE-HJ-NPR-Y0-9]{2,5})$|^([A-PR-Z]{3})([ACE-HJ-NPR-Y0-9]{2,5})$ | 19 / 33 | safe |
| CodeCornTech (euro-plate-validator) — une seule regex très large : accepte B, D, I, O, Z, « DW 00000 » et « DW 12AC3 » | ^[A-Z]{1,3}\s?[A-Z0-9]{4,5}$ | 21 / 33 | safe |
Implementation
const re = new RegExp("^(?:[A-PR-Z]{2} (?:(?!0{5})[0-9]{5}|(?!0{4})[0-9]{4}[ACE-HJ-NPR-Y]|(?!0{3})[0-9]{3}[ACE-HJ-NPR-Y]{2}|[1-9][ACE-HJ-NPR-Y](?!0{3})[0-9]{3}|[1-9][ACE-HJ-NPR-Y]{2}(?!00)[0-9]{2})|[A-PR-Z]{3} (?:[ACE-HJ-NPR-Y](?!0{3})[0-9]{3}|(?!00)[0-9]{2}[ACE-HJ-NPR-Y]{2}|[1-9][ACE-HJ-NPR-Y](?!00)[0-9]{2}|(?!00)[0-9]{2}[ACE-HJ-NPR-Y][1-9]|[1-9][ACE-HJ-NPR-Y]{2}[1-9]|[ACE-HJ-NPR-Y]{2}(?!00)[0-9]{2}|(?!0{5})[0-9]{5}|(?!0{4})[0-9]{4}[ACE-HJ-NPR-Y]|(?!0{3})[0-9]{3}[ACE-HJ-NPR-Y]{2}))$", "");
re.test(input);Limits
- Valide seulement la FORME : ne connaît pas la liste des powiats de l'annexe 13 du règlement. « XX 12345 » a la bonne forme sans qu'aucun powiat « XX » n'existe ; seule une table de préfixes, que ce motif ne reprend pas, le détecterait.
- Ne couvre que les plaques standard de voiture (§ 30 ust. 2 pkt 1). Les plaques individuelles (« D1 ABC », « D2 WXY12 »), de moto, de taille réduite, d'essai, d'exportation et diplomatiques suivent d'autres tableaux et sont rejetées.
- Le règlement distingue les préfixes à 2 lettres (powiat à lettre unique) et à 3 lettres, chacun avec ses tableaux de partie véhicule ; une regex ne peut pas savoir si « DW » est un préfixe à 2 lettres ou le début d'un préfixe à 3 lettres sans la liste des powiats. Le motif accepte les deux familles de formes pour tout préfixe de la bonne longueur.
- Basé sur le règlement du 8 novembre 2024 (Dz.U. 2024 poz. 1709) ; les modifications de 2025 et 2026 n'ont pas été relues et pourraient changer les tableaux.