Η μικρή σειρά άρθρων για τις κανονικές εκφράσεις έχει ολοκληρωθεί, αλλά θα ήταν μεγάλο λάθος να θεωρήσουμε ότι εξαντλήσαμε το θέμα. Γι’ αυτό και στο παρόν άρθρο θα μελετήσουμε μερικά ρεαλιστικά παραδείγματα, με ιδιαίτερη διδακτική αξία αλλά και χρησιμότητα. Στόχος μας είναι να αποκτήσουμε ολοκληρωμένη και στέρεη γνώση επί του θέματος, κι ο καλύτερος τρόπος για να τον πετύχουμε είναι προσπαθώντας να συνδυάσουμε όλα όσα μάθαμε.

Μετά τη γνωριμία με το συντακτικό των κανονικών εκφράσεων έφτασε η ώρα να μελετήσουμε ορισμένα ζωντανά παραδείγματα. Όπως έχουμε ήδη αναφέρει, οι κανονικές εκφράσεις υποστηρίζονται από πολλά εργαλεία τόσο της γραμμής εντολών, όσο και του περιβάλλοντος γραφικών. Εξάλλου, οι κανονικές εκφράσεις χρησιμοποιούνται και σε πολλές γλώσσες προγραμματισμού. Ως εκ τούτου, υπάρχουν αρκετές μηχανές κανονικών εκφράσεων, καθεμία από τις οποίες υποστηρίζει λιγότερα ή περισσότερα εκφραστικά σχήματα και, τέλος πάντων, έχεις τις δικές της ιδιαιτερότητες. Ο καθένας, ανάλογα με τα εργαλεία και τις γλώσσες προγραμματισμού που χρησιμοποιεί, οφείλει να εξοικειωθεί με την αντίστοιχη διάλεκτο. Εμείς, πάντως, θα στηριχτούμε στη μηχανή της Perl, που αποτελεί την ισχυρότερη και πιο διαδεδομένη υλοποίηση κανονικών εκφράσεων. Στη συνέχεια θα μελετήσουμε μερικά παραδείγματα εστιάζοντας στη λογική με την οποία συντάσσεται μια κανονική έκφραση. Επιπρόσθετα, θα υπογραμμίσουμε ορισμένα σημεία προσοχής που είχαμε επισημάνει και σε προηγούμενα άρθρα, καθώς κι άλλα που δεν αναφέραμε ποτέ αλλά παρουσιάζουν ξεχωριστό ενδιαφέρον. Θέλουμε να πιστεύουμε ότι όσα ακολουθούν θα επιδράσουν θετικά στη διάθεσή σας, τουλάχιστον όσο επέδρασαν και στη δική μας.

Apache redirection

Υπάρχει κανένας ανάμεσά μας που δεν έχει χρησιμοποιήσει τον Apache; Κατά πάσα πιθανότητα όχι. Αλλά κι αν ακόμα υπάρχει, το πιο πιθανό είναι ότι αργά ή γρήγορα θα παίξει κι αυτός με τον δημοφιλέστατο web server. Ας περάσουμε όμως στο θέμα μας. Ξεκινάμε με δεδομένο ότι έχουμε στήσει ένα site, άρα στο δίσκο του server υπάρχει και μια αντίστοιχη δομή καταλόγων με το σχετικό περιεχόμενο. Όλα πάνε καλά, ώσπου κάποια στιγμή αποφασίζουμε να τροποποιήσουμε τη δομή του site. Σε πολλές περιπτώσεις, αυτό σημαίνει ότι θα πρέπει να αλλάξουμε και τη διάρθρωση των καταλόγων. Εδώ όμως χρειάζεται λίγη προσοχή: Οι αλλαγές στη δομή των καταλόγων απαιτούν τις κατάλληλες αλλαγές σε όλα τα links που περιέχει το site, όπως επίσης και στα bookmarks που ενδέχεται να έχουν κρατήσει οι επισκέπτες μας. Όπως αντιλαμβάνεστε, ως διαχειριστές του site θα μπορούσαμε (έστω με τεράστιο κόπο) να διορθώσουμε όλα τα links που περιλαμβάνουν οι σελίδες του site. Ωστόσο, για τα bookmarks των επισκεπτών δεν θα μπορούσαμε να κάνουμε κάτι. Και κάπως έτσι μας προκύπτει ένα πρόβλημα: Αλλάζοντας τη δομή των καταλόγων στον server, ενδέχεται ορισμένοι από τους επισκέπτες μας να μη βρίσκουν αυτό που θέλουν – ακόμη κι αν έχουν κρατήσει bookmark. (Ίσως να σκέφτεστε ότι αυτό το σενάριο είναι εξειδικευμένο, αλλά αν στήσετε ποτέ το δικό σας site θα μας θυμηθείτε.)

Κάθε φορά που ο Apache δέχεται ένα αίτημα, συμβουλεύεται το αρχείο htaccess. Μεταξύ άλλων, αυτό το αρχείο περιλαμβάνει κανόνες που τροποποιούν τα URL των εισερχόμενων αιτημάτων. Όσοι έχετε παίξει με το Wordpress ή και με κάποιο άλλο CMS, είναι βέβαιο ότι έχετε ακούσει για το εν λόγω αρχείο, κατά τη ρύθμιση των λεγόμενων pretty URLs. Η λύση στο πρόβλημα που περιγράψαμε παραπάνω περνάει από την προσθήκη ενός κανόνα στο htaccess. Όπως υποψιάζεστε, σε αυτούς τους κανόνες μπορούμε να χρησιμοποιήσουμε κανονικές εκφράσεις. Ας υποθέσουμε ότι το site μας περιέχει ένα ιστολόγιο στη θέση mydomain.gr/my_blog, καθώς και ότι, σύμφωνα με τη νέα δομή, το ιστολόγιο κι όλο το περιεχόμενο του θα πρέπει να μεταφερθούν στη θέση mydomain.gr/tech_blog. Για να εξασφαλίσουμε ότι όλα τα παλιά links (αλλά και τα bookmarks των επισκεπτών) θα λειτουργούν σωστά, αρκεί να προσθέσουμε στο htaccess την ακόλουθη ενότητα:

RewriteEngine on
RewriteBase /
RewriteRule ^my_blog/(.*)$ http://domain.gr/tech_blog/$1 [L,R=Permanent] 

Όλη η ουσία βρίσκεται στον κανόνα τροποποίησης των URLs (RewriteRule). Εκεί χρησιμοποιούμε μια κανονική έκφραση, που περιγράφει όσα strings ξεκινούν με το my_blog και συνεχίζουν με οποιουσδήποτε άλλους χαρακτήρες. Ουσιαστικά, περιγράφουμε οποιοδήποτε URL δείχνει κάπου μέσα στην παλιά θέση του blog. Παρατηρείστε ότι στην κανονική έκφραση χρησιμοποιούμε τους χαρακτήρες .*, που περιγράφουν οποιοδήποτε string. Προσέξτε επίσης ότι τους έχουμε περικλείσει σε παρενθέσεις. Με αυτόν τον τρόπο δημιουργούμε ένα backreference, που περιέχει το εκάστοτε στοιχείο στο οποίο παραπέμπει το URL μέσα στον κατάλογο my_blog. Στη συνέχεια του κανόνα δηλώνουμε τη νέα μορφή για το URL. Εκεί, υποδεικνύουμε τη νέα θέση των αρχείων του blog κι αξιοποιούμε το backreference. Σημειώστε ότι ο αριθμός του backreference (στην περίπτωσή μας ο άσος) θα έπρεπε να τοποθετηθεί μετά από ένα backslash (\). Ωστόσο το συντακτικό του htaccess απαιτεί τη χρήση του δολαρίου ($). Δείτε μερικές μετατροπές που πραγματοποιεί αυτόματα ο παραπάνω κανόνας και θα κατανοήσετε αμέσως τι πετύχαμε:

mydomain.gr/my_blog/index.php       -> mydomain.gr/tech_blog/index.php
mydomain.gr/my_blog/images/pic1.jpg -> mydomain.gr/tech_blog/images/pic1.jpg
mydomain.gr/my_blog/hacks/index.php -> mydomain.gr/tech_blog/hacks/index.php

Μια μικρή βελτιστοποίηση

Κάθε φορά που χρησιμοποιούμε τις παρενθέσεις σε μία κανονική έκφραση, η εκάστοτε μηχανή δημιουργεί αυτόματα ένα αντίστοιχο backreference. Αυτή η διαδικασία όμως είναι σχετικά χρονοβόρα. Έτσι, όταν η κανονική έκφραση χρησιμοποιείται συχνά ή όταν περιλαμβάνει πολλά ζεύγη παρενθέσεων, η μηχανή επιβαρύνεται αρκετά. Η λύση σε αυτό το πρόβλημα είναι να απενεργοποιήσουμε τα backreferences εκεί όπου δεν τα χρειαζόμαστε. Για να χρησιμοποιήσουμε ένα ζεύγος παρενθέσεων και να εξασφαλίσουμε ότι η μηχανή των κανονικών εκφράσεων δεν θα δημιουργήσει ένα αντίστοιχο backreference, αρκεί να τοποθετήσουμε τους χαρακτήρες ?: αμέσως μετά την αριστερή παρένθεση.

Έλεγχος εισόδου

Το έχουμε ξαναπεί: Οι κανονικές εκφράσεις υποστηρίζονται από πολλές γλώσσες προγραμματισμού. Μία από αυτές είναι και η PHP, στην οποία οι κανονικές εκφράσεις συχνά χρησιμοποιούνται για τον έλεγχο των δεδομένων που εισάγει ο χρήστης, για μία αναζήτηση στα περιεχόμενα κάποιου αρχείου κ.ο.κ. Στη συνέχεια θα εξετάσουμε τις κανονικές εκφράσεις με τις οποίες μπορούμε να ελέγξουμε αν ο χρήστης έδωσε ένα αποδεκτό username, email και password. Βέβαια οι προδιαγραφές για ένα αποδεκτό username και password δεν είναι πάντοτε ίδιες. Κατανοώντας ωστόσο τη λογική με την οποία συντάξαμε τις σχετικές κανονικές εκφράσεις, θα μπορείτε να τις προσαρμόσετε εύκολα. Επίσης, αν και τα παραδείγματά μας βασίζονται στην PHP οι ίδιες κανονικές εκφράσεις θα λειτουργούν άψογα σε οποιαδήποτε γλώσσα υποστηρίζει τη μηχανή κανονικών εκφράσεων της Perl.

Όνομα χρήστη. Ας ξεκινήσουμε με τον έλεγχο για ένα αποδεκτό username. Ένα όνομα χρήστη επιτρέπεται να περιλαμβάνει πεζά γράμματα, αριθμητικά ψηφία, το σύμβολο της αφαίρεσης (-) ή και το λεγόμενο underscore (_). Οι υπόλοιποι χαρακτήρες απαγορεύονται. Επιπρόσθετα, δεχόμαστε ότι ένα όνομα χρήστη μπορεί να έχει ελάχιστο μήκος τους 3 χαρακτήρες και μέγιστο τους 16. Έχοντας αυτούς τους περιορισμούς κατά νου, δείτε το ακόλουθο προγραμματάκι:

$input = "jim49";
$regex = "/^[a-z0-9_-]{3,16}$/";

if (preg_match($regex, $input) == 1)
    echo "acceptable!";
else
    echo "un-a-ccept-a-ble!";

Στο παράδειγμά μας, το string που υποτίθεται ότι εισάγει ο χρήστης βρίσκεται στη μεταβλητή input. Αυτό έγινε για λόγους απλότητας και, τέλος πάντων, για να μην ξεφύγουμε από το θέμα μας. Στην πραγματικότητα, το string που δίνει ο χρήστης θα το παίρναμε από κάποια φόρμα ή με κάποιον άλλον, διαδραστικό μηχανισμό. Η κανονική έκφραση που περιγράφει ένα αποδεκτό όνομα χρήστη –σύμφωνα με τους κανόνες που αναφέραμε παραπάνω– βρίσκεται στη μεταβλητή regex. Να θυμίσουμε εδώ ότι κάθε φορά που δίνουμε μια κανονική έκφραση στην PHP, οφείλουμε να την ξεκινήσουμε και να την τερματίσουμε με το χαρακτήρα slash (/). Αυτός ο χαρακτήρας, λοιπόν, τοποθετείται κατ’ απαίτηση του συντακτικού της PHP και δεν πρέπει να τον μπλέκουμε με την εκάστοτε κανονική έκφραση.

Παρατηρείστε ότι η κανονική έκφραση ξεκινά και τελειώνει με τα anchors που ορίζουν την αρχή και το τέλος του string, αντίστοιχα. Η παρουσία της είναι κρίσιμη, διότι θέλουμε να εξασφαλίσουμε ότι όσα περιγράφει η κανονική έκφραση θα αποτελούν το σύνολο του δοθέντος string κι όχι κάποιο τμήμα του. Σκεφτείτε ότι θέλουμε να ελέγξουμε αν ο χρήστης έδωσε ένα αποδεκτό username κι όχι το αν έδωσε ένα string, το οποίο περιλαμβάνει ένα αποδεκτό username. Μετά από το anchor για την αρχή του string ακολουθεί μια κλάση χαρακτήρων. Σε αυτήν περιλαμβάνονται όλοι οι αποδεκτοί χαρακτήρες, που συμφωνήσαμε ότι επιτρέπονται στα usernames. Εδώ αξίζει να σταθούμε σε μια λεπτομέρεια: Ο χαρακτήρας του συμβόλου της αφαίρεσης (-) επιδέχεται ειδικής ερμηνείας μέσα στις κλάσεις, αφού χρησιμοποιείται για τον ορισμό περιοχών χαρακτήρων (π.χ., για τους χαρακτήρες από το a ως το z έχουμε την περιοχή a-z.) Προκειμένου να εξασφαλίσουμε ότι ο εν λόγω χαρακτήρας θα συμπεριληφθεί στην κλάση, τον τοποθετήσαμε στο τέλος της. Με αυτόν τον τρόπο εξασφαλίσαμε ότι θα ερμηνευτεί κυριολεκτικά, literally. Αμέσως μετά την κλάση ακολουθεί ένα ζεύγος από άγκιστρα. Όπως έχουμε πει, με τα άγκιστρα μπορούμε να δηλώσουμε το ελάχιστο και το μέγιστο πλήθος εμφανίσεων του χαρακτήρα που περιγράφεται ακριβώς πριν. Τελικά, η κανονική έκφραση του παραδείγματος περιγράφει ένα string το οποίο αποτελείται εξ ολοκλήρου από 3 έως 16 χαρακτήρες, καθένας από τους οποίους πρέπει να ανήκει στην κλάση που ορίσαμε. Απλό δεν ήταν; Σας προτείνουμε να πάτε μια βόλτα στο PHP Sandbox και πειραματιστείτε με το προγραμματάκι μας, δίνοντας διάφορες τιμές στη μεταβλητή input.

Διεύθυνση email. Το πρόβλημα το έχουμε εξετάσει και σε προηγούμενο άρθρο, αυτή τη φορά ωστόσο θα δούμε μια πιο εξελιγμένη κανονική έκφραση που περιγράφει εύστοχα οποιαδήποτε αποδεκτή διεύθυνση email. Παρεμπιπτόντως, οι κανόνες που δεχτήκαμε για μια πραγματική διεύθυνση email έχουν ως εξής: Το σχετικό string ξεκινά με ένα όνομα χρήστη, το οποίο περιλαμβάνει τους χαρακτήρες που περιγράψαμε στο προηγούμενο παράδειγμα, καθώς και εκείνον της τελείας. Επίσης, το όνομα χρήστη μπορεί να έχει οποιοδήποτε μήκος. Στη συνέχεια εμφανίζεται το περίφημο παπάκι (@). Ακολουθεί το domain name, το οποίο μπορεί να περιλαμβάνει μόνο αριθμητικά ψηφία, γράμματα, τελείες και τον χαρακτήρα της αφαίρεσης (την παύλα). Στο τέλος εμφανίζεται μια τελεία κι αμέσως μετά το λεγόμενο TLD (Top Level Domain), το οποίο ενδέχεται να περιλαμβάνει γράμματα, έχει μήκος 2 έως 6 χαρακτήρες κι ενδέχεται να περιλαμβάνει τον χαρακτήρα της τελείας. Δείτε την κανονική έκφραση που περιγράφει τα παραπάνω:

^[a-zA-Ζ0-9_\.-]+@[a-zA-Ζ0-9\.-]+\.[a-zA-Ζ\.]{2,6}$

Όπως και στο προηγούμενο παράδειγμα, η κανονική έκφραση ξεκινά και τελειώνει με τα σχετικά anchors (^ και $). Βλέπετε, θέλουμε και πάλι να εξασφαλίσουμε ότι η κανονική έκφραση θα περιγράφει το σύνολο του δοθέντος string κι όχι μόνο ένα τμήμα του. Μετά το πρώτο anchor ακολουθεί μία κλάση με όλους τους χαρακτήρες που μπορεί να περιλαμβάνει το username σε μια διεύθυνση email. Μετά την κλάση ακολουθεί ο χαρακτήρας του συμβόλου της πρόσθεσης (+), που δηλώνει ότι ο αμέσως προηγούμενος χαρακτήρας (δηλαδή κάποιος από αυτούς που περιγράφει η κλάση) εμφανίζεται τουλάχιστον μία φορά. Με άλλα λόγια, το username περιλαμβάνει τουλάχιστον έναν χαρακτήρα. Έπεται το παπάκι κι αμέσως μετά δηλώνουμε πάλι μια κλάση με τους χαρακτήρες που επιτρέπονται στα domain names. Μετά από την κλάση έχουμε τοποθετήσει και πάλι τον χαρακτήρα της πρόσθεσης (το domain name έχει τουλάχιστον έναν χαρακτήρα). Ακολουθεί ο χαρακτήρας της τελείας και μία κλάση με τους χαρακτήρες που επιτρέπονται στο TLD. Τα άγκιστρα εξασφαλίζουν ότι το μήκος του TLD θα είναι από 2 έως 6 χαρακτήρες.

Σε αυτό το παράδειγμα, πέρα από τη γενικότερη λογική που ακολουθήσαμε πρέπει να παρατηρήσετε και τη χρήση του χαρακτήρα backslash (\). Όπως έχουμε ξαναπεί, ο συγκεκριμένος χαρακτήρας πραγματοποιεί το λεγόμενο character escaping. Με απλά λόγια, καταργεί την ειδική ερμηνεία που επιδέχεται ο αμέσως επόμενος χαρακτήρας. Στην έκφραση που συγκροτήσαμε παραπάνω χρησιμοποιούμε το χαρακτήρα της τελείας σε αρκετά σημεία, αλλά πάντοτε με την κυριολεκτική του ερμηνεία και ποτέ με την έννοια του μπαλαντέρ. Ακριβώς γι’ αυτό, οπουδήποτε έχουμε χρησιμοποιήσει την τελεία έχουμε τοποθετήσει στ’ αριστερά της κι ένα backslash.

Συνθηματικό. Ας υποθέσουμε τώρα ότι η εφαρμογή ζητά από το χρήστη να ορίσει ένα ισχυρό συνθηματικό. Όταν ο χρήστης δίνει το επιθυμητό συνθηματικό, η εφαρμογή ελέγχει αν είναι όντως ισχυρό ή όχι, οπότε το αποδέχεται ή το απορρίπτει. Όπως υποψιάζεστε, γι’ αυτόν τον έλεγχο θα επιστρατεύσουμε και πάλι μια κατάλληλα σχηματισμένη κανονική έκφραση. Πριν προχωρήσουμε, όμως, πρέπει να συμφωνήσουμε στα γνωρίσματα που διακρίνουν ένα ισχυρό συνθηματικό. Εμείς δεχτήκαμε τους ακόλουθους περιορισμούς:

  • θα πρέπει να περιέχει τουλάχιστον δύο πεζά γράμματα
  • θα πρέπει να περιέχει τουλάχιστον δύο κεφαλαία γράμματα
  • θα πρέπει να περιέχει τουλάχιστον τρία αριθμητικά ψηφία
  • θα πρέπει να περιέχει τουλάχιστον έναν χαρακτήρα που δεν ανήκει στους χαρακτήρες λέξεων (ως χαρακτήρες λέξεων, word characters, θεωρούνται όλα τα πεζά και τα κεφαλαία γράμματα, τα αριθμητικά ψηφία και το underscore)

Τα πράγματα περιπλέκονται. Το συνθηματικό είναι πιθανό να περιλαμβάνει σχεδόν οποιονδήποτε χαρακτήρα (που μπορεί να πληκτρολογήσει ο χρήστης) και, φυσικά, μπορεί να ‘χει οποιαδήποτε μορφή. Δεν γνωρίζουμε ούτε τη σειρά με την οποία εμφανίζονται οι χαρακτήρες, ούτε το ακριβές πλήθος κάθε κατηγορίας χαρακτήρων, ούτε τίποτα. Λογικό, θα σκεφτείτε, εφόσον πρόκειται για ένα συνθηματικό. Πώς λοιπόν θα καταφέρουμε να περιγράψουμε ένα string, για το οποίο γνωρίζουμε μόνο ελάχιστα, γενικά χαρακτηριστικά;

Εδώ θα πρέπει να σκεφτούμε πονηρά. Εφόσον το συνθηματικό μπορεί να έχει οποιαδήποτε μορφή, θα ξεκινήσουμε με μία κανονική έκφραση η οποία περιγράφει οποιοδήποτε string! Πανεύκολο: .*. Αυτοί οι δύο χαρακτήρες αποτελούν μια εξαιρετικά απλή κανονική έκφραση, που περιγράφει τα πάντα. Τώρα μένει να εξασφαλίσουμε ότι αυτά “τα πάντα” ικανοποιούν τους περιορισμούς που θέσαμε για τη συγκρότηση ενός ισχυρού συνθηματικού. Για τους σχετικούς ελέγχους θα αξιοποιήσουμε το μηχανισμό lookahead. Όπως έχουμε πει, τo lookahead αποτελεί έναν μηχανισμό με τον οποίο μπορούμε να τσεκάρουμε αν εμφανίζεται κάποιο string ή όχι. Από την έκβαση αυτού του ελέγχου καθορίζεται το αν θα συνεχιστεί η επεξεργασία της υπόλοιπης κανονικής έκφρασης ή αν θα διακοπεί. Στη δεύτερη περίπτωση, η μηχανή των κανονικών εκφράσεων θεωρεί ότι η αναζήτηση με τη συγκεκριμένη έκφραση απέτυχε. Στο σενάριο που εξετάζουμε, θα χρησιμοποιήσουμε το lookahead για να ελέγξουμε αν το string που έδωσε ο χρήστης περιλαμβάνει τουλάχιστον δύο πεζά γράμματα, τουλάχιστον δύο κεφαλαία, τουλάχιστον τρία αριθμητικά ψηφία και τουλάχιστον έναν χαρακτήρα – από εκείνους που δεν συγκαταλέγονται στα word characters. (Στο εξής, τους χαρακτήρες αυτού του είδους θα τους αποκαλούμε “άσχετους”.) Ας δούμε κάθε έλεγχο ξεχωριστά.

  • Έλεγχος για τουλάχιστον δύο πεζά γράμματα. Τα δύο πεζά γράμματα μπορούν να βρίσκονται οπουδήποτε μέσα στο string κι ανάμεσά τους να βρίσκονται οποιοιδήποτε άλλοι χαρακτήρες. Επομένως, το σχετικό lookahead πρέπει να είναι κάπως έτσι: (?=.*[a-z].*[a-z]). Αυτό το lookahead ελέγχει για την ύπαρξη ενός string, το οποίο ξεκινά με οποιουσδήποτε χαρακτήρες, συνεχίζει με ένα πεζό γράμμα, στη συνέχεια εμφανίζει πάλι οποιουσδήποτε χαρακτήρες και στο τέλος περιλαμβάνει ένα ακόμα πεζό γράμμα.
  • Έλεγχος για τουλάχιστον δύο κεφαλαία γράμματα. Όπως και στην περίπτωση των πεζών, τα δύο κεφαλαία γράμματα δεν είναι υποχρεωτικό να βρίσκονται σε συγκεκριμένη περιοχή ούτε και σε διαδοχικές θέσεις. Δείτε το σχετικό lookahead: (?=.*[A-Z].*[A-Z]).
  • Έλεγχος για τουλάχιστον τρία αριθμητικά ψηφία. Ακολουθώντας την ίδια λογική, συντάσσουμε ένα lookahead το οποίο ελέγχει αν εμφανίζονται τρία αριθμητικά ψηφία με οποιουσδήποτε άλλους χαρακτήρες ανάμεσά τους: (?=.*\d.*\d.*\d). Το μόνο καινούργιο στοιχείο εδώ είναι ο συνδυασμός των χαρακτήρων \d. Με αυτούς τους χαρακτήρες αναφερόμαστε σε όλα τα αριθμητικά ψηφία. Με άλλα λόγια, το \d αποτελεί μια συντομογραφία της κλάσης χαρακτήρων [0-9].
  • Έλεγχος για τουλάχιστον έναν άσχετο χαρακτήρα. Οι χαρακτήρες που θεωρείται ότι συμμετέχουν σε μία λέξη περιγράφονται από την εξής κλάση: [a-zA-Z0-9_]. Επομένως, οι άσχετοι χαρακτήρες είναι εκείνοι που ανήκουν στη συμπληρωματική κλάση. Πρόκειται, δηλαδή, για τους χαρακτήρες που ανήκουν στην κλάση [^a-zA-Z0-9_]. Όπως έχουμε πει, ο χαρακτήρας caret αντιστρέφει τα περιεχόμενα μια κλάσης ή, αν προτιμάτε, ορίζει το συμπλήρωμά της. Παρεμπιπτόντως, η τελευταία κλάση μπορεί να γραφτεί ακόμα πιο σύντομα ως εξής: [^\w]. Οι χαρακτήρες \w αποτελούν μια συντομογραφία της κλάσης [a-zA-Z0-9_]. Έχοντας αυτά κατά νου, το lookahead που ελέγχει για την ύπαρξη τουλάχιστον ενός άσχετου χαρακτήρα έχει την ακόλουθη μορφή: (?=.*[^\w]).

Τέλεια. Για την ώρα έχουμε α) μία κανονική έκφραση που περιγράφει κάθε string και β) τέσσερα lookaheads, καθένα από τα οποία ελέγχει αν ικανοποιείται κάποιος από τους περιορισμούς που έχουμε θέσει. Αυτό που μένει είναι τα συνδυάσουμε:

^(?=.*[a-z].*[a-z])(?=.*[A-Z].*[A-Z])(?=.*?\d.*?\d.*?\d)(?=.*?[^\w]).*

Το αποτέλεσμα είναι μάλλον ψαρωτικό. Η παραπάνω έκφραση ξεκινά από την αρχή του string και πραγματοποιεί τους τέσσερις ελέγχους που περιγράφουν τα lookaheads. Αν έστω και ένας έλεγχος αποτύχει, η επεξεργασία της κανονικής έκφρασης θα διακοπεί. Διαφορετικά, αν οι έλεγχοι ολοκληρωθούν με επιτυχία, η επεξεργασία της κανονικής έκφρασης θα συνεχιστεί απρόσκοπτα. Μετά από τα τέσσερα lookaheads ακολουθούν οι χαρακτήρες .*, που περιγράφουν οποιοδήποτε string. Επομένως, αν η επεξεργασία της κανονικής έκφρασης συνεχιστεί, καταλήγουμε στην περιγραφή ενός οποιουδήποτε string.

Τελικά, αυτό που χρειάζεται να κάνουμε στο πρόγραμμά μας είναι να χρησιμοποιήσουμε τη συγκεκριμένη κανονική έκφραση και να προχωρήσουμε σε μια αναζήτηση εντός του string που έδωσε ο χρήστης. Αν η αναζήτηση δεν επιστρέψει τίποτα, συμπεραίνουμε ότι κάποιος από τους περιορισμούς μας δεν ικανοποιείται. Αν όμως η αναζήτηση επιστρέψει κάτι, συμπεραίνουμε ότι όλοι οι περιορισμοί μας ικανοποιούνται. Είδατε; Το αν ικανοποιούνται οι περιορισμοί για τη συγκρότηση ενός ισχυρού password, το κρίνουμε από το αν ικανοποιούνται οι έλεγχοι των lookahead. Κι όσο για το αν ικανοποιούνται οι έλεγχοι των lookahead, αυτό το κρίνουμε από το αν η κανονική έκφραση που περιγράφει τα πάντα (.*) επιστρέφει κάτι ή όχι. Δείτε το σχετικό πρόγραμμα:

$input = "oKje5R!6z";
$regex = "/ ^(?=.*[a-z].*[a-z])(?=.*[A-Z].*[A-Z])(?=.*?\d.*?\d.*?\d)(?=.*?[^\w]).*/";
if (preg_match($regex, $input) == 1)
    echo "Strong password!";
else
    echo "Weak password!";

Μία ακόμα βελτιστοποίηση

Για το τέλος αξίζει να σταθούμε σε μια σημαντική λεπτομέρεια. Όπως θα προσέξατε, η προηγούμενη κανονική έκφραση ξεκινά με το anchor που συμβολίζει την αρχή του string (^). Αυτό το anchor τοποθετείται ώστε τα τέσσερα lookaheads να πραγματοποιηθούν από μία φορά και μόνο, με τους σχετικούς ελέγχους να ξεκινούν από την αρχή του string. Χωρίς το anchor, η μηχανή των κανονικών εκφράσεων θα δοκίμαζε τα lookaheads σε κάθε υπό-string του αρχικού string. Αυτό δεν θα άλλαζε καθόλου το τελικό αποτέλεσμα, αλλά θα αύξανε δραματικά το φόρτο εργασίας της μηχανής. Ηθικό δίδαγμα: Εκτός ελαχίστων περιπτώσεων, είναι φρόνιμο να καρφώνουμε τα lookaheads σε συγκεκριμένες θέσεις, ώστε να εκτελούνται μόνο μια φορά. Μ’ αυτόν τον τρόπο η εργασία της εκάστοτε μηχανής εκφράσεων θα ελαχιστοποιείται ενώ την ίδια στιγμή η αποτελεσματικότητά της δεν θα επηρεάζεται στο ελάχιστο.

Άρθρα της σειράς

Μέρος 14: special chars, escaping, classes, ranges, negation, repetition

Μέρος 24: anchors, concatenation, alternation, eagerness, backreference

Μέρος 34: lookahead, lookbehind, greedy machines, lazy machines

Μέρος 44: backreference, anchors, character escaping, lookahead

~Spir@l Evolut10n

Σας άρεσε το post; Αν ναι μπορείτε να στηρίξετε το ðhacker, χωρίς κατ’ ανάγκη να ξοδέψετε χρήματα.