Skip to content

Instantly share code, notes, and snippets.

@christiaanwesterbeek
Last active November 24, 2023 10:53
Show Gist options
  • Star 16 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save christiaanwesterbeek/c574beaf73adcfd74997 to your computer and use it in GitHub Desktop.
Save christiaanwesterbeek/c574beaf73adcfd74997 to your computer and use it in GitHub Desktop.
Splitsen van een nederlands adres naar straat, huisnummer en toevoeging middels een regular expression. Deutsch-Adressen werden jetzt auch unterstützt.
let re = /^(\d*[\wäöüß\d '\/\\\-\.]+)[,\s]+(\d+)\s*([\wäöüß\d\-\/]*)$/i
let adressen = [
'Dorpstraat 2',
'Dorpstr. 2',
'Laan 1933 2',
'18 Septemberplein 12',
'Kerkstraat 42-f3',
'Kerk straat 2b',
'42nd street, 1337a',
'1e Constantijn Huigensstraat 9b',
'Maas-Waalweg 15',
'De Dompelaar 1 B',
'Kümmersbrucker Straße 2',
'Friedrichstädter Straße 42-46',
'Höhenstraße 5A',
'Saturnusstraat 60-75',
'Saturnusstraat 60 - 75',
'Plein \'40-\'45 10',
'Plein 1945 1',
'Steenkade t/o 56',
'Steenkade a/b Twee Gezusters',
'1, rue de l\'eglise'
]
let matches = adressen.map((adres) => {
let match = adres.match(re)
return `<td>${adres}</td><td>${match && match.shift() && match.join('</td><td>')}</td>`
})
document.write(`<table><tr>${matches.join('</tr><tr>')}</tr></table>`)
// jsFiddle version here: https://jsfiddle.net/devotis/3wLv7ex2
// Changes 2017-08-23
// allow / and \ in street names
// use es6 and drop the semicolons
// refactor code to produce html table
@christiaanwesterbeek
Copy link
Author

Added support for matching abbreviations like xxxxstr. for xxxxstraat

@christiaanwesterbeek
Copy link
Author

Deutsch-Adressen werden jetzt auch unterstützt.

@DHFW
Copy link

DHFW commented Jan 4, 2016

@devotis, thank you for this! Very helpful to do some data cleaning!

Remarks:

  1. For best matching results use a trimmed input string.
  2. In case the street contains extra spaces ("Somestreet 120"), the result of street group is "Somestreet ", so also trim the groups as well!

I encountered an address like this:
p/a Repelsestraat 8

So use this regex to also tackle forward and backwards slashes in the street name:
^(\d*[\wäöüß\d '\/\\\-\.]+)[,\s]+(\d+)\s*([\wäöüß\d\-\/]*)$

@christiaanwesterbeek
Copy link
Author

Thanks @DHFW I updated the gist.

@JoryHogeveen
Copy link

JoryHogeveen commented Oct 5, 2017

Minor update to allow all common special characters in western europe, both lowercase and uppercase:

äáàâåöóòôüúùûëéèêïíìîýÿÄÁÀÂÖÓÒÔÜÚÙÛËÉÈÊÏÍÌÎÝßñÑÇçšæÆ

I use this in PHP:

function get_street_parts( $v ) {
	$c = 'äáàâåëéèêïíìîöóòôüúùûýÿ'
	   . 'ÄÁÀÂËÉÈÊÏÍÌÎÖÓÒÔÜÚÙÛÝß'
   	   . 'ñÑÇçšæÆ';
	$r = "/^(\d*[\w{$c}\d '\/\-\.]+)[,\s]+(\d+)\s*([\w{$c}\d\-\/]*)$/i";
	preg_match( $r, $v, $m );
	return $m;
}

@rhengeveld
Copy link

This is precisely the regex I was looking for. Thank you very much! 👍

@jochemfuchs
Copy link

For whoever needs it, I've adapted the Regex to accept all types of letters, not just those specifically listed above.
/^(\d*[\p{L}\d '\/\\\-\.]+)[,\s]+(\d+)\s*([\p{L}\d\-\/]*)$/iu

The key part is \p{L} and the /iu in the end.
See: https://www.php.net/manual/en/regexp.reference.unicode.php

@JayMcAnser
Copy link

JayMcAnser commented Apr 29, 2020

Thanks. Works great, but still missing some:

This regex solves these problems:

   /^(\d*[\p{L}\d '\/\\\-\.]+)[,\s]+(\d+)\s*([\p{L} \d\-\/'"\(\)]*)$/iu
  • Nieuwe gracht 22zw /2
  • Nieuwe gracht 224 2
  • Joubertstraat 17, 2e verdieping
  • Graaf FLorisstraat 20 4- hoog
  • Jacob van Lennepkade 53- I"
  • 1e Jan van der Heijdenstraat 77-2 A
  • Lange Slachterijstraat 22 bus II
  • Hoogte Kadijk 44 - C
  • Grevelingenstr 116 (2)

(edit: missed the ' in the regEx)

@christiaanwesterbeek
Copy link
Author

christiaanwesterbeek commented Apr 29, 2020

Het wordt tijd voor een echte Github repo met tests!

@JayMcAnser
Copy link

Klinkt als een goed idee. Er zijn altijd zoveel uitzonderingen, we kunnen nooit genoeg testen.

@christiaanwesterbeek
Copy link
Author

christiaanwesterbeek commented Apr 30, 2020

Hoe moeten we voorkeur geven aan regex A of B?

regex                  | A                           | B                             |
adres                  | straat        |  hnr | ext  | straat            | hnr | ext |
--------------------------------------------------------------------------------------
Nieuwe gracht 224 2    | Nieuwe gracht |  224 | 2    | Nieuwe gracht 224 | 2   |     |
Plein 1945 1           | Plein         | 1945 | 1    | Plein   1945      | 1   |     |

Met regex A gaat Nieuwe gracht goed, maar met regex B gaat Plein 1945 goed.

Ik heb in een officiële postcode tabel 711 verschillende straatnamen gevonden met minimaal 2 cijfers erin. Een greep:

A 73 Rijksweg
A76
Antwerpseweg - N281
Apollo 11-Laan
Laan '40-'45
Laan 1509
Laan 1813
Laan 1914
Laan 1917
Laan 1933
Laan 1940 - 1945
Laan 1940-'45
Laan 1940-1945
Laan 1944
Laan 1945
Laan 1948
Laan 1954
Laan van de 17e september
Plein '40-'45
Plein 1 febr 1953
Plein 13
Plein 1455
Plein 15 Augustus
Plein 1799
Plein 1803
Plein 1813
Plein 1817
Plein 1918
Plein 1923
Plein 1940
Plein 1940-1945
Plein 1944
Plein 1945
Plein 1945-1995
Plein 1946
Plein 1953
Plein 1957
Plein 1969
Plein 1992
Plein 1999
Plein 2000
Plein 40-45
Plein 983
Plein Loods 24
Plein Spanje '36-'39
Provinciale weg N206
Provinciale weg N216
Provinciale Weg T48
Provincialeweg 20
Provincialeweg 43
Provincialeweg A325
Provincialeweg N 209
Provincialeweg N201
...
Provincialeweg N338
Punter 10
...
Punter 49
Randstad 20
...
Randstad 23
Rijksweg 11
...
Rijksweg 75 A2
Rijksweg A12
...
Rijksweg N65
Rijksweg Nr 42
Rijksweg S13
Rijksweg-A58
Rijksweg-A67
Ringweg Zuid / Rijksweg A20
Rozengaard 11
...
Rozengaard 19
S-18
Schoener 10
...
Schoener 50
Schouw 12
...
Schouw 54
September 1944-straat
Singel 1940-1945
Tjalk 10
...
Tjalk 42
Top 40-plein
Tunnel 1883
Verzetstraat 1940-1945
Vosseveld 1988
Weg 1940-1945
Wold 10
...
Wold 28
Zonegge 01
...
Zonegge 24
Zoom 10
...
Zoom 20

En dan nog een hele reeks straatnamen beginnend met 2 cijfers zoals 12e Septemberlaan, maar dat is geen probleem.

@JayMcAnser
Copy link

Eigenlijk zou de "Nieuwe gracht 224 2" twee keer door de regEx moeten. Als er dan opnieuw een nummer ontstaat was deze nog niet volledig verwerkt. Geen idee hoe je dat in een RegEx moet doen. De regex gaat wel goed als de 2 (vermoedelijk de verdieping) aangegeven wordt als -2 of /2 of 2hoog.

Dit lost natuurlijk niet het probleem van "Plein 1944 2" op. Tussen deze en "Nieuwe gracht 224 2" is geen verschil te zien.

@rubentebogt
Copy link

Het wordt tijd voor een echte Github repo met tests!

Hi @christiaanwesterbeek voor PHP testing heb ik vast een begin gemaakt zie: https://github.com/WebRTB/address-splitter

Is wel nog steeds W.I.P. :-) pull requests zijn meer dan welkom, als het een beetje draait zal ik hem publiceren op composer voor iedereen.

@NoxxieNl
Copy link

NoxxieNl commented Jul 9, 2020

Waarom niet eens een Lexer proberen wellicht in plaats van in een regex te krijgen?

Ik weet hij is nog niet perfect, maar veel al werkt "redelijk". (en nee dit is een half uur lexer werk ;-))

Maakt gebruik van de doctrine/lexer package :-).

<?php
namespace App\Lexer;

use Doctrine\Common\Lexer\AbstractLexer;

class CharacterTypeLexer extends AbstractLexer
{
    const T_LETTER = 1;
    CONST T_NUMBER = 2;
    CONST T_SPACE = 3;
    CONST T_DELMITER = 4;

    CONST T_UNKNOWN = 99;

    protected function getCatchablePatterns()
    {
        return [];
    }

    protected function getNonCatchablePatterns()
    {
        return [];
    }

    protected function getType(&$value)
    {
        if (preg_match('/\p{L}+/', $value)) {
            return self::T_LETTER;
        }

        elseif (preg_match('/[0-9]/', $value)) {
            return self::T_NUMBER;
        }

        elseif (preg_match('/[:\-,]/', $value)) {
            return self::T_DELMITER;
        }

        elseif ($value === ' ') {
            return self::T_SPACE;
        }
        else {
            return self::T_UNKNOWN;
        }
    }
}
<?php
namespace App\Lexer;

use App\Lexer\CharacterTypeLexer;

class TestEvaluater
{
    private $lexer;

    private $splittedData = [
        'street' => null,
        'number' => null,
        'addition' => null
    ];

    public function __construct(CharacterTypeLexer $lexer)
    {
        $this->lexer = $lexer;
    }

    public function getSplittedAddress($string)
    {
        $this->lexer->setInput($string);
        $this->lexer->moveNext();

        $this->lookingFor = 'street';

        while (true) {

            if (!$this->lexer->lookahead) {
                break;
            }

            $this->lexer->moveNext();


            if ($this->lexer->token['type'] == CharacterTypeLexer::T_SPACE) {
                if (! is_null($lookahead = $this->lexer->lookahead)) {

                    if ($lookahead['type'] !== CharacterTypeLexer::T_LETTER) {
                        
                        if ($this->lookingFor == 'street') {
                            // Check if this number is not part of the streetname
                            $stillStreet = false;

                            while (true) {
                                if (is_null($peek = $this->lexer->peek())) {
                                    break;
                                }

                                if ($peek['type'] == CharacterTypeLexer::T_DELMITER) {
                                    break;
                                }
                                
                                if ($peek['type'] == CharacterTypeLexer::T_SPACE) {
                                    if (! is_null($numberPeek = $this->lexer->peek())) {
                                        if ($numberPeek['type'] == CharacterTypeLexer::T_NUMBER) {
                                            $stillStreet = true;
                                        }
                                    }
                                }
                            }
                        }

                        if (! $stillStreet) {
                            $this->moveToNextLooking();
                        } else {
                            $this->splittedData[$this->lookingFor][] = $this->lexer->token['value'];
                            $stillStreet = false;
                        }
                    } else {
                        
                        if ($this->lookingFor == 'number') {
                            $this->moveToNextLooking();
                        } else {
                            $this->splittedData[$this->lookingFor][] = $this->lexer->token['value'];
                        }
                    }
                }
            } else {
                $this->splittedData[$this->lookingFor][] = $this->lexer->token['value'];

                if (! is_null($lookahead = $this->lexer->lookahead)) {
                    if ($lookahead['type'] == CharacterTypeLexer::T_LETTER && $this->lookingFor == 'number') {
                        $this->moveToNextLooking();
                    }
                }
            }
        }

        return $this->splittedData;
    }

    protected function moveToNextLooking() : void
    {
        if ($this->lookingFor == 'street') {
            $this->lookingFor = 'number';
        } elseif ($this->lookingFor == 'number') {
            $this->lookingFor = 'addition';
        }
    }
}

Gebruik:

use App\Lexer\TestEvaluater;
use App\Lexer\CharacterTypeLexer;

$test = new TestEvaluater(new CharacterTypeLexer());

$test->getSplittedAddress('adres');

@Remco75
Copy link

Remco75 commented Sep 4, 2020

@Noxxie
Omdat dat PHP is? Daarmee bedoel ik: deze regex kan je in de meeste talen gewoon parsen. Zou mooi zijn als dit idd een repo wordt, maar dan puur voor de regex, met testen. die wellicht zelfs meerdere talen ondersteund? Ik wil wel helpen als @christiaanwesterbeek dat nog gaat oppakken :-)

@NoxxieNl
Copy link

NoxxieNl commented Sep 4, 2020

@Remco75 het is wellicht nog niet perfect maar https://github.com/NoxxieNl/Nladdresslexer

@zentek-jannes-grueneberg

We figured out a few problems with german addresses, where the house number is a range or even written like 5+6.
Here is an updated Regex.
RE: ^(\d*[\p{L}\d '\/\\\-\.]+?)[,\s]+((?:\d+)\s*(?:[\p{L}\d\/]*(?:\s*[-+]\s*[\p{L}\d\-+\/]*)?))$
RE with sample data: https://regex101.com/r/BaQUN4/1
Bye

@NickB23
Copy link

NickB23 commented Jan 12, 2023

We figured out a few problems with german addresses, where the house number is a range or even written like 5+6. Here is an updated Regex. RE: ^(\d*[\p{L}\d '\/\\\-\.]+?)[,\s]+((?:\d+)\s*(?:[\p{L}\d\/]*(?:\s*[-+]\s*[\p{L}\d\-+\/]*)?))$ RE with sample data: https://regex101.com/r/BaQUN4/1 Bye

Here's the python equivalent if anyone is interested:

r"^(\d*[\w\d '\/\\\-\.]+?)[,\s]+((?:\d+)\s*(?:[\w\d\/]*(?:\s*[-+]\s*[\w\d\-+\/]*)?))$"

@drummerik
Copy link

Beste collega's -g, wanneer het huisnummer + toevoeging bijvoorbeeld 24-2 is, wordt deze niet goed gesplitst in de regex die ik gebruik. De 2 wordt dan als huisnummer gezien. Is hier al een oplossing voor? Groet

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment