Regex, dubbele tekens vermijden?
Een vraagje ... is het mogelijk (en zo ja hoe) om via een regex dubbele tekens af te keuren?
Wat ik graag zou willen is dat ik bijv. kan aangeven dat er letters en cijfers gebruikt mogen worden, en spaties, punten en streepjes. Dan krijg je zoiets:
pattern="^[- .0-9A-Za-z]$"
Maar ik wil niet dat men dubbele spaties, streepjes en punten kan gebruiken.
Dus dit mag wel: Ozzie.PHP-Hulp
Maar dit niet: Ozzie..PHP--Hulp
Is het mogelijk om in een regex aan te geven dat er alleen 'enkele' streepjes, spaties en punten gebruikt mogen worden?
http://www.google.nl/search?q=php+regex+remove+duplicate+characters
Deze bevat de oplossing :
http://stackoverflow.com/a/10342485
Paste het wat aan :
Code (php)
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
<?php
$n = 'Ozzie..PHP--Hulp';
echo '<p>' . $n . '</p>';
$n = preg_replace('/([\.-\s])\\1+/', '$1', $n);
echo '<p>' . $n . '</p>';
?>
$n = 'Ozzie..PHP--Hulp';
echo '<p>' . $n . '</p>';
$n = preg_replace('/([\.-\s])\\1+/', '$1', $n);
echo '<p>' . $n . '</p>';
?>
Gewijzigd op 26/12/2016 18:27:31 door Adoptive Solution
Adoptive Solution op 26/12/2016 18:26:06:
Paste het wat aan :
Code (php)
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
<?php
$n = 'Ozzie..PHP--Hulp';
echo '<p>' . $n . '</p>';
$n = preg_replace('/([\.-\s])\\1+/', '$1', $n);
echo '<p>' . $n . '</p>';
?>
$n = 'Ozzie..PHP--Hulp';
echo '<p>' . $n . '</p>';
$n = preg_replace('/([\.-\s])\\1+/', '$1', $n);
echo '<p>' . $n . '</p>';
?>
Bron output hier: <p>Ozzie..PHP--Hulp</p><p></p>
Wel, Ozzie heeft vastwel een 'goed basisboek' waar het in staat hoe je het doet.
Toevoeging op 26/12/2016 19:30:22:
Ik heb geen goed boek, moest dus even in mijn eigen archief bladeren.
Dit moet het wel goed doen :
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
$string = 'Oz zie.....PHP----Hulp';
echo '<p>' . $string . '</p>' . PHP_EOL;
$patterns = array();
$patterns[0] = '/\.+/';
$patterns[1] = '/-+/';
$patterns[2] = '/\s+/';
$replacements = array();
$replacements[0] = '.';
$replacements[1] = '-';
$replacements[2] = ' ';
echo '<p>' . preg_replace($patterns, $replacements, $string) . '</p>' . PHP_EOL;
?>
$string = 'Oz zie.....PHP----Hulp';
echo '<p>' . $string . '</p>' . PHP_EOL;
$patterns = array();
$patterns[0] = '/\.+/';
$patterns[1] = '/-+/';
$patterns[2] = '/\s+/';
$replacements = array();
$replacements[0] = '.';
$replacements[1] = '-';
$replacements[2] = ' ';
echo '<p>' . preg_replace($patterns, $replacements, $string) . '</p>' . PHP_EOL;
?>
http://adoptive.esy.es/remove-double.php
De vraag is hoe ik dus een negatieve match krijg. Er hoeft niks vervangen te worden. De regex moet simpelweg niet slagen als er .. of -- of dubbele spatie in staat.
Ik denk dat ik het maar ga oplossen met lookaheads.
Toevoeging op 26/12/2016 21:27:04:
PS
>> Wel, Ozzie heeft vastwel een 'goed basisboek' waar het in staat hoe je het doet.
Wat was de bedoeling van die opmerking?
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php
$pattern = '/
^
(?:
[[:alnum:]] # letters + cijfers
|
(?:
([.\s-]) # punt, spatie en streepje
(?!\1) # niet gevolgt door hetzelfde teken
)
)+
$
/x';
$tests = ['Ozzie..PHP--Hulp', 'Ozzie.PHP-Hulp', 'a.b.c.'];
foreach ($tests as $t) {
if (preg_match($pattern, $t)) {
echo 'OK '.$t.PHP_EOL;
} else {
echo 'NO '.$t.PHP_EOL;
}
}
?>
$pattern = '/
^
(?:
[[:alnum:]] # letters + cijfers
|
(?:
([.\s-]) # punt, spatie en streepje
(?!\1) # niet gevolgt door hetzelfde teken
)
)+
$
/x';
$tests = ['Ozzie..PHP--Hulp', 'Ozzie.PHP-Hulp', 'a.b.c.'];
foreach ($tests as $t) {
if (preg_match($pattern, $t)) {
echo 'OK '.$t.PHP_EOL;
} else {
echo 'NO '.$t.PHP_EOL;
}
}
?>
Dit staat bijv. "Ozzie.-PHP-Hulp" wel toe, als dat ook niet mag wordt de pattern:
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
$pattern = '/
^
(?:
[[:alnum:]] # letters + cijfers
|
(?:
[.\s-] # punt, spatie en streepje
(?:[^.\s-]|$) # gevolgt door of een *niet* punt, spatie, streepje of het einde van de string
)
)+
$
/x';
?>
$pattern = '/
^
(?:
[[:alnum:]] # letters + cijfers
|
(?:
[.\s-] # punt, spatie en streepje
(?:[^.\s-]|$) # gevolgt door of een *niet* punt, spatie, streepje of het einde van de string
)
)+
$
/x';
?>
Gewijzigd op 26/12/2016 22:45:09 door Wouter J
Wat doet nu precies die "?:" ?
Toevoeging op 26/12/2016 23:32:02:
Dit stukje ?:[[:alnum:]] werkt bij mij alleen als ik het zo doe
?:[0-9A-Za-z]
Dus zonder die dubbele blokhaken. Heb je toevallig enig idee waarom? Ook zie ik geen verschil als ik in dit stukje ?: weglaat. Het resultaat lijkt dan gewoon hetzelfde te zijn.
(PS ik gebruik het als een pattern in een html5 input-veld)
?: zorgt ervoor dat de capture group niet wordt opgeslagen, zie de resulterende array wanneer je preg_match gebruikt.
Ben van Velzen op 27/12/2016 00:01:07:
?: zorgt ervoor dat de capture group niet wordt opgeslagen, zie de resulterende array wanneer je preg_match gebruikt.
Wat is daarvan precies (wel of juist niet) het nut?
Ik wil dus enkel valideren (html5 input pattern) of iemand iets geldigs heeft ingevuld.
Quote:
(PS ik gebruik het als een pattern in een html5 input-veld)
Dat verklaard het [[:alnum:]] stukje :) HTML5 input velden gebruiken de JavaScript regex engine. Deze ondersteund geen speciale character classes zoals [:alnum:] (wat gewoon een short-hand is voor [\w\d] in PHP).
Quote:
Wat doet nu precies die "?:" ?
Normaal gesproken wordt een capture group "gecaptured" (opgeslagen). Dit betekend dat we er later weer naar kunnen referreren doormiddel van zijn index. Bijvoorbeeld:
Deze indexen kun je dan weer gebruiken in de regex. Bijvoorbeeld /([a-z])\1/ zal 2 keer dezelfde letter achter elkaar matchen.
Dit opslaan van de capture group kost wat geheugen en wat tijd. Door (?:...) ipv (...) te gebruiken vertel je dat de capture group niet opgeslagen moet worden. Op deze manier wordt je regex iets sneller. Dit is waarschijnlijk vooral pas merkbaar bij grote hoeveelheden matchen, dus in dit geval een grote lengte van de string.
Zie ook http://www.regular-expressions.info/brackets.html , een hele handige site voor informatie over alle syntaxen in regex en het optimaliseren daarvan.
Gewijzigd op 27/12/2016 00:07:01 door Wouter J
Nog een laatste vraag.
Nog even uitgaande van die voorgaande regex ...
Dit stukje (?:[^.\s-]|$) vond ik op zich wel mooi ... dat je dus ook niet bijv. .- kunt krijgen. Het nadeel is nu echter dat je ipv .- wel bijv. een vreemd teken kunt zetten bijv .* wat ik eigenlijk niet wil. Is dat nog (makkelijk) op te lossen?
Idealiter zou ik (ongeveer) dit willen:
BEGIN en EINDIG de string met een letter of getal. Daartussen zijn de enige toegestane karakters letters en cijfers en de volgende vreemde tekens: spatie streepje punt ... maar die mogen niet na elkaar volgen, met als enige uitzondering een punt die wél gevolgd mag worden door een spatie. Heb je daar nog een tip voor hoe ik dat moet aanpakken?
Dit mag:
Ozzie PHPhulp
Ozzie.PHP-hulp
Ozzie. PHP-hulp
Maar dit mag niet:
.Ozzie (begint niet met een letter of getal)
Ozzie- (eindigt niet met een letter of getal)
Ozzie.-PHPhulp (punt en streepje na elkaar mag niet)
Oftewel: Een alnum, gevolgd door of een alnum of een speciaal teken die op zijn minst weer wordt gevolgd door een alnum. En dat laatste dan 0 of meer keer. Misschien iets leesbaarder in een parsing grammer syntax:
Code (php)
1
2
3
4
2
3
4
String <- Alnum AlnumOrSpecial*
AlnumOrSpecial <- Alnum / (Special Alnum)
Alnum <- [a-zA-Z0-9]
Special <- [-. ]
AlnumOrSpecial <- Alnum / (Special Alnum)
Alnum <- [a-zA-Z0-9]
Special <- [-. ]
Gewijzigd op 27/12/2016 00:40:24 door Wouter J
Toevoeging op 27/12/2016 01:32:24:
PS
Kan ik dit
(?:[a-zA-Z0-9]|[.\s\-a-zA-Z0-9][a-zA-Z0-9])*
ook vervangen door
(?:[.\s\-]?[a-zA-Z0-9])*
En zo ja, is het een beter dan het ander of maakt dat niks uit?
Maar voor simpele HTML5 input validatie is dat allemaal over-optimalisatie. En zou dan wel voor jouw optie kiezen, een stuk leesbaarder naar mijn idee.
Ik wil eigenlijk toch terug naar de eerste variant die jij had voorgesteld. Dus geen dezelfde vreemde tekens achter elkaar, maar verschillende vreemde tekens achter elkaar mag wel.
Dus niet 2x een spatie achter elkaar maar een punt en een spatie mag bijvoorbeeld wel.
Ik vond dit zelf wel een hele toffe oplossing van jou:
Het enige nadeel is nu dat ik na een punt, streepje of spatie een gek teken kan zetten. Je zou dus bijvoorbeeld .* kunnen doen, terwijl een sterretje niet is toegestaan.
Wat ik dus zou willen, is dit:
We hebben vreemde tekens: [.\s-]
En cijfers en letters: [0-9a-zA-Z]
Cijfers en letters mogen gewoon achter elkaar. Geen enkel probleem.
Maar een vreemd tegen moet gevolgd worden door óf een cijfer of letter óf een ANDER vreemd teken.
Is dat op een handige manier te doen? Het mooiste zou zijn als je kunt zeggen ... een vreemd teken moet gevolgd worden door [.-\s0-9a-zA-Z] maar dan zonder datzelfde vreemde teken. Ik kan wel voor ieder vreemd teken een aparte groep maken waar dat teken door gevolgd moet worden, maar dan wordt de regex zo enorm lang (in de praktijk zijn het zelfs nog een paar meer vreemde tekens).
Maar goed, dus even samengevat.
Cijfers en letters mogen elkaar gewoon opvolgen, maar een vreemd teken mag niet door datzelfde vreemde teken worden gevolgd, maar wel door een ander vreemd teken of een cijfer of letter.
Weet je daar nog iets voor? Dat zou geweldig zijn.
Dus ([.\s-])(?!\1) matched alleen de punt, spatie of streepje. Niet het teken daarna (het controleert alleen of het teken daarna niet hetzelfde teken is). Dus /^(?:[a-zA-Z0-9]|([.\s-])(?!\1|$))+$/ zal moeten werken naar mijn idee.
https://jsbin.com/jabiloxoju/edit?js,output
Ik snap alleen niet waarom "abc." een negatieve match oplevert. Die zou toch moeten kloppen?
De lookahead is (?!\1|$) oftewel: Niet \1 of $. Dus niet het einde van de string. Dat is precies wat je wou, toch?
Thanks Wouter. Je hebt me weer wat bijgeleerd! :-)
Mogelijk zou je ook aan een alternatieve oplossing kunnen denken waarbij je niet-legale karakters replaced (maar vermijd blacklists omdat je dan mogelijk niet alle ongewenste gevallen vangt) en dan na afloop deze (potentieel aangepaste) string vergelijkt met het origineel. Indien de twee strings verschillen wil dat zeggen dat er illegale karakters gestript zijn en dus dat de invoer ongeldig was.
Bij het zoeken naar een oplossing ben je niet per definitie beperkt tot de kaders van je regexp. En mogelijk maakt dat het regexp-deel een stuk eenvoudiger ;).
Gewijzigd op 28/12/2016 16:57:36 door Thomas van den Heuvel
Thanks, ik zal dat straks nog eens proberen, hoewel ik meen ergens gelezen te hebben dat dat voor een html5 input pattern niet werkt, maar zeker weten doe ik dat niet meer.
>> Bij het zoeken naar een oplossing ben je niet per definitie beperkt tot de kaders van je regexp. En mogelijk maakt dat het regexp-deel een stuk eenvoudiger ;).
Klopt inderdaad, maar in sommige situaties vind ik een regex wel de "mooiere" oplossing. Vooral omdat het hier echt om een bepaald patroon gaat, en in mindere mate om de vraag 'zit dat teken er wel of niet in'. Maar wat je zegt klopt zeker. Er zijn meerdere mogelijkheden.