Inledning
Reguljära uttryck, på engelska regular expressions, är ett kraftfullt sätt att bearbeta texter och hitta mönster i texter. Reguljära uttryck finns i de flesta programmeringsspråk så som PHP, Java, javaScript etc. Finns stödet inte från början så finns det garanterat bibliotek att ladda hem till alla övriga språk.
I C# finns stödet för reguljära uttryck i namnrymden System.Text.RegularExpressions. Klasserna vi kommer att undersöka är främst Regex, Match och MatchCollection.
Är du sedan tidigare van vid reguljära uttryck finns det vissa detaljer i .NET som kan skilja sig åt. Överlag så är implementationen av reguljära uttryck i .NET omfattande. För detaljer se Using Regular Expressions with The Microsoft .NET Framework
Inledande exempel
Vad är då ett reguljärt uttryck? Jo det är en textsträng som bildar ett mönster. Detta mönster kan innehålla styrtecken, även kallade metatecken. Vi börjar med ett exempel där vi vill undersöka förekomsten av "ring" i ett utdrag från Sagan om Ringen. Vi använder ett enkelt reguljärt uttryck utan några styrtecken.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string text = @"Three Rings for the Elven-kings under the sky,
Seven for the Dwarf-lords in their halls of stone,
Nine for Mortal Men doomed to die,
One for the Dark Lord on his dark throne
In the Land of Mordor where the Shadows lie.
One Ring to rule them all, One Ring to find them,
One Ring to bring them all and in the darkness bind them
In the Land of Mordor where the Shadows lie.";
MatchCollection matches = Regex.Matches(text, "ring");
Console.WriteLine("Hittade " + matches.Count + " förekomst(er).\n");
text = Regex.Replace(text, "ring", "XXXX");
Console.WriteLine(text);
}
}
}
På rad 22 fångar vi upp en MatchCollection från Regex.Matches(text, "ring"). Metoden Matches() ger oss en samling av alla förekomster som hittas. I vårt exempel skickar vi in den text som skall undersökas samt det reguljära uttryck som skall användas, i vårt fall "ring". Vi använder sedan Regex.Replace() metoden för att byta ut förekomsten av "ring" mot "XXXX" så att vi tydligare kan se var förekomsterna finns i texten.
Vad händer? Jo, den enda förekomst vi fann var i "bring" i slutet av texten. Inte riktigt så vi hade tänkt oss. Alla förekomster av "Ring" ignoreras.
Detta beror på att vi det görs skillnad på stora och små bokstäver, dvs. "Ring" är inte samma sak som "ring". På engelska kallas detta för case sensitive. Vi gör nu om exemplet och låter sökningen ingorera skillnaden mellan stora och små bokstäver (case insensitive) samtidigt skapar vi ett objekt av typen Regex för att slippa ange det reguljära uttrycket på två ställen (vid Mathces och Replace).
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string text = @"Three Rings for the Elven-kings under the sky,
Seven for the Dwarf-lords in their halls of stone,
Nine for Mortal Men doomed to die,
One for the Dark Lord on his dark throne
In the Land of Mordor where the Shadows lie.
One Ring to rule them all, One Ring to find them,
One Ring to bring them all and in the darkness bind them
In the Land of Mordor where the Shadows lie.";
Regex regex = new Regex("ring", RegexOptions.IgnoreCase);
MatchCollection matches = regex.Matches(text);
Console.WriteLine("Hittade " + matches.Count + " förekomst(er).\n");
text = regex.Replace(text, "XXXX");
Console.WriteLine(text);
}
}
}
Nu när vi skapar ett reguljärt uttryck utan att gå vi klassen Regex direkt, så har vi anget flaggan IgnoreCase (rad 22). Resultatet blir nu 5 förekomster. Tyvärr hittar vi fortfarande "bring". Eftersom vi skapade objektet regex så måste vi gå via objektet för att använda Matches() och Replace().
Specialtecken
Det finns en hel del specialtecken. Vi börjar med att lista några av de vanligaste.
Tecken | Betydelse |
---|---|
. | Alla tecken utom ny rad |
\b | Början eller slutet av ett ord |
\s | Alla blanktecken, t.ex. tab, mellanslag och ny rad |
\w | Alla alfanumeriska tecken (både bokstäver och siffror) |
\d | Alla siffertecken (0-9) |
^ | Början på strängen |
$ | Slutet på strängen |
Det är nu viktigt att komma ihåg att tecknet \ vanligtvis inleder en escape sekvens för strängar. Det betyder att skall skriva \b i en sträng så får du skriva "\\b"!
Strängen "\\" tolkas som texten "\". Ett effektivare sätt att skriva reguljära uttryck är att använda ordagranna strängar (verbatim strings). Se artikeln om Stränghantering för mer info.
Om du vill hitta förekomsten av tecken som är specialtecken, t.ex. punkter (.) så måste du skriva @"\." som reguljärt uttryck. Tecknet \ inleder specialtecken i reguljära uttryck men används också i kombination för att kunna skriva tecken som . + ? * ^ och $ i mönstret utan att de skall tolkas som specialtecken.
Vi modifierar nu Exempel 2 genom att använda specialtecknet \b. Med ett reguljärt uttryck som @"\bring\b" (glöm inte att använda ordagranna strängar) blir resultatet:
Nu matchas inte "bring" längre. Tyvärr så matchas inte längre texten "Rings" (plural) utan vi får 3 förekomster. Detta leder oss till en ny del av reguljära uttryck...
Repetition
Vi kan ange hur många gånger ett tecken eller en sekvens av tecken får förekomma i vårt mönster. Följande specialtecken har med repetition att göra:
Tecken | Betydelse |
---|---|
* | Upprepa obegränsat antal gånger |
+ | Upprepa en eller fler gånger |
? | Upprepa noll eller en gång |
{n} | Upprepa exakt n gånger (där n är ett heltal) |
{n,m} | Upprepa minst n gånger men allra högst m gånger (m måste vara större än n) |
{n,} | Upprepa minst n gånger |
Nu när vi har gått igenom en rad användbara specialtecken skall vi se på några snabba exempel innan vi går vidare med vårt exempel om Sagan om Ringen.
@"\b\w{5,6}\b" hittar alla ord som är 5 eller 6 bokstäver långa.
@"\b\d{2,4}-\d{5,6}" hittar telefonnummer där riktnumret är 2-4 siffror följt av bindetecken och sedan 5-6 siffror. T.ex. "08-123456" eller "0415-54321".
@"\d{6}-\d{4}" hittar alla personnummer".
@"^\w*" hittar första ordet i en text".
Använder vi det vi lärt oss och anpassar vårt reguljära uttryck i Exempel 2 till @"\brings?\b" får vi:
Genom att använda ? blir betydelsen att "s":et kan förekomma 0 eller 1 gång. På så vis matchas både "ring" och "rings" i vår text.
Teckenklasser
Vi har tittat på specialtecken för siffror och bokstäver eller bara siffror men tänk om vi vill ha en speciell blandning av tillåtna tecken? Genom att använda [] kan vi ange vilka tecken som skall gälla. Använder vi "[aeiouyåäö]" som reguljärt uttryck så letar vi efter förekomster av vokaler. Exempel:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Console.Write("Skriv in en text: ");
string text = Console.ReadLine();
MatchCollection matches = Regex.Matches(text, "[aeiouyåäö]", RegexOptions.IgnoreCase);
Console.WriteLine("Hittade " + matches.Count + " vokaler");
}
}
}
Vi kan också ange intervaller av tecken, t.ex. [a-z0-9] betyder alla små bokstäver a till z och alla siffror 0 till 9. Ett annat exempel är t.ex. [a-fA-F] vilket ger alla stora och små bokstäver från A till F.
Om vi vill leta efter tecken som avslutar en mening (kanske räkna meningar?) skulle nog [.!?] fungera. Observera att tecknen . och ? förlorar sin betydelse i en teckenklass.
Alternativ
Tecknet | anger alternativ vilket är logist då det betyder "eller" i vanliga fall i C#. Vi tar ett exempel.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Console.Write("Mata in en veckodag: ");
string text = Console.ReadLine();
Regex regex = new Regex("^(måndag|tisdag|onsdag|torsdag|fredag|lördag|söndag)$", RegexOptions.IgnoreCase);
if (regex.IsMatch(text))
Console.WriteLine("Korrekt!");
else
Console.WriteLine("Fel!");
}
}
}
Här används ^ och $ för att se till att vi matar in exakt något av de alternativ vi angett inom parentesen. Mellan början och slutet på texten så tillåts bara någon av de sju alternativ vi angett (veckodagarna). Ett exempel på hur reguljära uttryck kan användas för att validera text, dvs. att säkerställa ett visst format på inmatningen. Vi använder metoden IsMatch() som enkelt kollar om det finns minst en matchning,
Gruppering
För att skapa grupper i ett reguljärt uttryck så använd parenteser (). Vi använde parenteser första gången i exempel 4 för att kunna använda alternativ mellan början och slutet på texten. Vi kan använda gruppering för att t.ex. ange repetition av mer än ett tecken t.ex:
@"(\d{1,3}\.){3}\d{1,3}" hittar enkla IP-adresser. Första grupperingen tillåter 1-3 siffror och en punkt vilket skall upprepas exakt 3 gånger. Sedan följer 1-3 siffor. Texten "192.168.0.32" skulle matchas.
Grupperingar kan också användas för att göra en backreference till tidigare matchade delar i samma reguljära uttryck. Exempel:
@"\b(\w+)\b\s*\1\b" letar efter repeterade ord t.ex. "Får får lamm?" där "Får får" skulle matcha. \1 hänvisar till den första grupperingens (\w+) värde. Detta är mer ett exempel på hur krångligt reguljära uttryck kan vara. Fast ju mer du lär dig desto oftare skulle du kalla det "kraftfullt" i stället för "krångligt".
Negation
Fler specialtecken eller kombinationer som är motsatsen till tidigare specialtecken.
Tecken | Betydelse |
---|---|
\W | Alla tecken som INTE är alfanumeriska |
\S | Alla tecken som INTE är blanktecken |
\D | Alla tecken som INTE är siffertecken |
\B | En position som INTE är början eller slutet på ett ord |
[^x] | Alla tecken som INTE är x |
[^ieyo] | Alla tecken som INTE någon av tecknen i,e,y,o |
Regex tester
Det finns många program som kan hjälpa dig att testa reguljära uttryck på ett stycke text. Vi har valt att göra ett enkelt exempel i Wondows Forms där du kan mata in en text, ett reguljärt uttryck och sedan få ett resultat.
Obs: Vi har IgnoreCase som standard på alla reguljära uttryck. Ändringar får göras manuellt i koden.
Källkoden finns här:
Vad tog vi inte upp?
Eftersom det skrivs böcker i ämnet reguljära uttryck så är det självklart att vi inte kan ta upp alla finesser och detaljer. Denna artikel kan ses som en inledande förklaring till reguljära uttryck. Vidare studier måste göras om du vill bli riktigt duktig i ämnet.
Saker som vi inte tog upp var bl. a:
Fler specialtecken
Positive Lookaround
Negative Lookaround
Greedy / Lazy