<?php
// https://pt.stackoverflow.com/q/257031/53463
$re = '/
# Definição de ordens
(?(DEFINE)(?P<unidades> um|dois|tr[eê]s|quatro|cinco|meia|se(?:is|te)|oito|nove ))
(?(DEFINE)(?P<dezenas_dez> d(?:ez(?:e(?:nove|sse(?:is|te))|oito)?|oze)|onze|treze|catorze|quinze ))
(?(DEFINE)(?P<dezenas_exceto_dez> vinte|trinta|(?:quar|cinqu|se(?:ss|t)|oit|nov)enta ))
(?(DEFINE)(?P<dezenas> (?P>dezenas_dez)|(?P>dezenas_exceto_dez) ))
(?(DEFINE)(?P<dezenas_ou_unidades> (?P>dezenas)|(?P>unidades) ))
(?(DEFINE)(?P<centenas> ce(?:m|ntos?)|(?:qu(?:atroc|inh)|(?:du|tre|se(?:is|te)|oito|nove)[cz])entos? ))
(?(DEFINE)(?P<mil> mil ))
(?(DEFINE)(?P<milhoes> (?:(?:[mb]|tr)i|qu(?:atri|inqua))lh(?:[aã]o|[oõ]es) ))
(?(DEFINE)(?P<s> [ ]+ ))
(?(DEFINE)(?P<e> e(?P>s) ))
#Regras
\b(?:
#1. Entre dezenas>=20 e unidades
(?P>dezenas_exceto_dez)(?P>s)(?=(?P>unidades)\b)
|
#2. Entre centenas e dezenas ou unidades
(?P>centenas)(?P>s)(?=(?P>dezenas_ou_unidades)\b)
|
#3-4. Entre milhares...
(?P>mil)(?P>s)(?=
(?:
#3. ... e dezenas ou unidades
(?P>dezenas_ou_unidades)
|
#4. ... e centenas, somente se não houver dezenas ou unidades
(?P>centenas)(?!(?P>s)(?P>e)?(?P>dezenas_ou_unidades)\b)
)
\b
)
|
#5. Entre milhões e dezenas ou unidades (que não sejam unidades ou dezenas de milhões ou unidades ou dezenas de milhares)
(?P>milhoes)(?P>s)(?=
(?P>dezenas_ou_unidades)(?!(?P>s)(?P>e)?(?:(?P>unidades)(?P>s)(?P>e)?)?(?:(?P>milhoes)|(?P>mil))\b)\b
)
)
\b
#Reset
\K
/iux' ;
$subst = 'e ' ;
//Exemplos
$teste = [
'vinte cinco' ,
'vinte cinco mil' ,
'vinte cinco reais' ,
'vinte cinco reais e vinte cinco centavos' ,
'aaa bbb ccc' ,
'dois mil trezentos' ,
'dois mil trezentos quarenta cinco' ,
'dois milhões trinta um' ,
'dois milhões trinta um mil' ,
'dois milhões trinta mil' ,
'dois milhões trinta um' ,
'cento vinte três milhões quatrocentos cinquenta seis mil setecentos oitenta nove'
] ;
//Substituir
//Mostrar em uma única matriz
PD9waHAKCi8vIGh0dHBzOi8vcHQuc3RhY2tvdmVyZmxvdy5jb20vcS8yNTcwMzEvNTM0NjMKCiRyZSA9ICcvCgkJIyBEZWZpbmnDp8OjbyBkZSBvcmRlbnMKCQkoPyhERUZJTkUpKD9QPHVuaWRhZGVzPiAgICAgICAgICAgIHVtfGRvaXN8dHJbZcOqXXN8cXVhdHJvfGNpbmNvfG1laWF8c2UoPzppc3x0ZSl8b2l0b3xub3ZlICAgICAgICAgICAgICAgICAgICAgICAgKSkKCQkoPyhERUZJTkUpKD9QPGRlemVuYXNfZGV6PiAgICAgICAgIGQoPzpleig/OmUoPzpub3ZlfHNzZSg/OmlzfHRlKSl8b2l0byk/fG96ZSl8b256ZXx0cmV6ZXxjYXRvcnplfHF1aW56ZSAgICAgICAgICApKQoJCSg/KERFRklORSkoP1A8ZGV6ZW5hc19leGNldG9fZGV6PiAgdmludGV8dHJpbnRhfCg/OnF1YXJ8Y2lucXV8c2UoPzpzc3x0KXxvaXR8bm92KWVudGEgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkpCgkJKD8oREVGSU5FKSg/UDxkZXplbmFzPiAgICAgICAgICAgICAoP1A+ZGV6ZW5hc19kZXopfCg/UD5kZXplbmFzX2V4Y2V0b19kZXopICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSkKCQkoPyhERUZJTkUpKD9QPGRlemVuYXNfb3VfdW5pZGFkZXM+ICg/UD5kZXplbmFzKXwoP1A+dW5pZGFkZXMpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApKQoJCSg/KERFRklORSkoP1A8Y2VudGVuYXM+ICAgICAgICAgICAgY2UoPzptfG50b3M/KXwoPzpxdSg/OmF0cm9jfGluaCl8KD86ZHV8dHJlfHNlKD86aXN8dGUpfG9pdG98bm92ZSlbY3pdKWVudG9zPyAgICkpCgkJKD8oREVGSU5FKSg/UDxtaWw+ICAgICAgICAgICAgICAgICBtaWwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSkKCQkoPyhERUZJTkUpKD9QPG1pbGhvZXM+ICAgICAgICAgICAgICg/Oig/OlttYl18dHIpaXxxdSg/OmF0cml8aW5xdWEpKWxoKD86W2HDo11vfFtvw7VdZXMpICAgICAgICAgICAgICAgICAgICAgICAgICAgICkpCgkJKD8oREVGSU5FKSg/UDxzPiAgICAgICAgICAgICAgICAgICBbIF0rICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSkKCQkoPyhERUZJTkUpKD9QPGU+ICAgICAgICAgICAgICAgICAgIGUoP1A+cykgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApKQoJCQoJCQoJCSNSZWdyYXMKCQlcYig/OgoJCQoJCSAgICAjMS4gRW50cmUgZGV6ZW5hcz49MjAgZSB1bmlkYWRlcwoJCSAgICAoP1A+ZGV6ZW5hc19leGNldG9fZGV6KSg/UD5zKSg/PSg/UD51bmlkYWRlcylcYikKCQl8CgkJICAgICMyLiBFbnRyZSBjZW50ZW5hcyBlIGRlemVuYXMgb3UgdW5pZGFkZXMKCQkgICAgKD9QPmNlbnRlbmFzKSg/UD5zKSg/PSg/UD5kZXplbmFzX291X3VuaWRhZGVzKVxiKQoJCXwKCQkgICAgIzMtNC4gRW50cmUgbWlsaGFyZXMuLi4KCQkgICAgKD9QPm1pbCkoP1A+cykoPz0KCQkgICAgICAgICg/OgoJCSAgICAgICAgICAgICMzLiAuLi4gZSBkZXplbmFzIG91IHVuaWRhZGVzCgkJICAgICAgICAgICAgKD9QPmRlemVuYXNfb3VfdW5pZGFkZXMpCgkJICAgICAgICB8CgkJICAgICAgICAgICAgIzQuIC4uLiBlIGNlbnRlbmFzLCBzb21lbnRlIHNlIG7Do28gaG91dmVyIGRlemVuYXMgb3UgdW5pZGFkZXMKCQkgICAgICAgICAgICAoP1A+Y2VudGVuYXMpKD8hKD9QPnMpKD9QPmUpPyg/UD5kZXplbmFzX291X3VuaWRhZGVzKVxiKQoJCSAgICAgICAgKQoJCSAgICAgICAgXGIKCQkgICAgKQoJCXwKCQkgICAgIzUuIEVudHJlIG1pbGjDtWVzIGUgZGV6ZW5hcyBvdSB1bmlkYWRlcyAocXVlIG7Do28gc2VqYW0gdW5pZGFkZXMgb3UgZGV6ZW5hcyBkZSBtaWxow7VlcyBvdSB1bmlkYWRlcyBvdSBkZXplbmFzIGRlIG1pbGhhcmVzKQoJCSAgICAoP1A+bWlsaG9lcykoP1A+cykoPz0KCQkgICAgICAgICg/UD5kZXplbmFzX291X3VuaWRhZGVzKSg/ISg/UD5zKSg/UD5lKT8oPzooP1A+dW5pZGFkZXMpKD9QPnMpKD9QPmUpPyk/KD86KD9QPm1pbGhvZXMpfCg/UD5taWwpKVxiKVxiCgkJICAgICAgICAKCQkgICAgKQoJCQoJCSkKCQlcYgoJCQoJCSNSZXNldAoJCVxLCgkJCgkvaXV4JzsKCQkKJHN1YnN0ID0gJ2UgJzsKCgovL0V4ZW1wbG9zCSAgIAokdGVzdGUgPSBbCgkJCSd2aW50ZSBjaW5jbycsCgkJCSd2aW50ZSBjaW5jbyBtaWwnLAoJCQkndmludGUgY2luY28gcmVhaXMnLAoJCQkndmludGUgY2luY28gcmVhaXMgZSB2aW50ZSBjaW5jbyBjZW50YXZvcycsCgkJCSdhYWEgYmJiIGNjYycsCgkJCSdkb2lzIG1pbCB0cmV6ZW50b3MnLAoJCQknZG9pcyBtaWwgdHJlemVudG9zIHF1YXJlbnRhIGNpbmNvJywKCQkJJ2RvaXMgbWlsaMO1ZXMgdHJpbnRhIHVtJywKCQkJJ2RvaXMgbWlsaMO1ZXMgdHJpbnRhIHVtIG1pbCcsCgkJCSdkb2lzIG1pbGjDtWVzIHRyaW50YSBtaWwnLAoJCQknZG9pcyBtaWxow7VlcyB0cmludGEgdW0nLAoJCQknY2VudG8gdmludGUgdHLDqnMgbWlsaMO1ZXMgcXVhdHJvY2VudG9zIGNpbnF1ZW50YSBzZWlzIG1pbCBzZXRlY2VudG9zIG9pdGVudGEgbm92ZScKCQldOwoJCQovL1N1YnN0aXR1aXIKJHJlc3VsdGFkbyA9IHByZWdfcmVwbGFjZSgkcmUsICRzdWJzdCwgJHRlc3RlKTsKCgovL01vc3RyYXIgZW0gdW1hIMO6bmljYSBtYXRyaXoKJHJlc3VsdGFkb19maW5hbCA9IGFycmF5X2NvbWJpbmUoICR0ZXN0ZSwgJHJlc3VsdGFkbyk7CnZhcl9leHBvcnQoJHJlc3VsdGFkb19maW5hbCk7
stdout
array (
'vinte cinco' => 'vinte e cinco',
'vinte cinco mil' => 'vinte e cinco mil',
'vinte cinco reais' => 'vinte e cinco reais',
'vinte cinco reais e vinte cinco centavos' => 'vinte e cinco reais e vinte e cinco centavos',
'aaa bbb ccc' => 'aaa bbb ccc',
'dois mil trezentos' => 'dois mil e trezentos',
'dois mil trezentos quarenta cinco' => 'dois mil trezentos e quarenta e cinco',
'dois milhões trinta um' => 'dois milhões e trinta e um',
'dois milhões trinta um mil' => 'dois milhões trinta e um mil',
'dois milhões trinta mil' => 'dois milhões trinta mil',
'cento vinte três milhões quatrocentos cinquenta seis mil setecentos oitenta nove' => 'cento e vinte e três milhões quatrocentos e cinquenta e seis mil setecentos e oitenta e nove',
)