|
Dit artikel is gepubliceerd op zondag 31 juli 2011 op vbvoorbeelden, bezoek de website voor een recente versie van dit artikel of andere artikels.
27.9.1. Backreferencing with Grouping ConstructsMen kan in een pattern naar een capture van een eerder gedefinieerde group verwijzen aan de hand van een "backreference". Dit kan je bijvoorbeeld doen door een backslash \ te laten volgen door het nummer van de groep. De grouping constructs ( tenzij ze "noncapturing" zijn ) worden van links naar rechts opeenlopend genummerd. Let op : eerst worden van links naar rechts de "unnamed" grouping constructs genummerd, daarna van links naar rechts de "named" grouping constructs. In pattern (?<group1>abc)(def) zal group1 de derde groep ( op index 2 ) zijn, en (def) de tweede ( op index 1 ). Het volledige pattern wordt als de eerste groep ( op index 0 ) gezien. Dit is specifiek voor de .NET regex engine, de ander bekende engines nummeren named en unnamed grouping constructs zonder onderscheid.
Noncapturing groups worden niet door de regex engine onthouden en zijn bijgevolg ook bevorderlijk voor de performantie, maar er kan verderop in de reguliere expressie niet meer naartoe verwezen worden. Een grouping construct is by default capturing, men maakt ze noncapturing door tussen de haakjes met ?: te starten. Visual Basic 2010 Broncode Imports System.Text.RegularExpressions Class Example1 Public Shared Sub Main() Dim input As String = "<a href=""url"">link</a><b>bold</b>" Dim tagPattern As String = "<([a-zA-Z][a-zA-Z0-9]*)\b[^>]*>.*?</\1>" Dim match As Match For Each match In Regex.Matches(input, tagPattern) Console.WriteLine(match.Value) Next input = "abcdef" match = Regex.Match(input, "(?<group1>abc)(def)") Dim group1 As Group = match.Groups.Item(0) Console.WriteLine(group1.Value) Dim group2 As Group = match.Groups.Item(1) Console.WriteLine(group2.Value) Dim group3 As Group = match.Groups.Item(2) Console.WriteLine(group3.Value) Console.ReadLine() End SubEnd ClassDownload Visual Basic 2010 Broncode Download Visual C# Sourcecode
Console Application Output <a href="url">link</a>
<b>bold</b>
abcdef
def
abc Ook naar de naam van een grouping construct kunnen we verwijzen, hierbij wordt de backslash gevolgd door deze naam.
De GroupCollection opgeleverd door de Groups property van een Match object beschikt over een Items property waar men zowel via de index als via de naam, een groep ( Group object ) kan ophalen : Visual Basic 2010 Broncode Imports System.Text.RegularExpressions Class Example2 Public Shared Sub Main() Dim input As String = "<a href=""url""><i>link</i></a><b>bold</b>" Dim tagPattern As String tagPattern = "<(?<name>[a-zA-Z][a-zA-Z0-9]*)\b[^>]*>(?<value>.*?)</\<name>>" Dim matches As MatchCollection = Regex.Matches(input, tagPattern) For Each match As Match In Regex.Matches(input, tagPattern) Console.WriteLine( "Tag : " & match.Value) Console.WriteLine( "Name : " & match.Groups.Item( "name").Value) Console.WriteLine( "Value : " & match.Groups.Item( "value").Value) Console.WriteLine() Next Console.ReadLine() End SubEnd ClassDownload Visual Basic 2010 Broncode Download Visual C# Sourcecode
Console Application Output Tag : <a href="url">link</a>
Name : a
Value : link
Tag : <b>bold</b>
Name : b
Value : bold In volgend voorbeeld vindt het pattern op regel (1) vind de tekst "b", de grouping construct voor de "b" is immers optioneel ( ? ).
Ook pattern op regel (2) vind de tekst "b", de grouping construct matcht hier omdat de "a" ook nul keer mag voorkomen ( a? ), de deelmatch (a?) levert niets op, maar faalt ook niet. Deze deelmatch ( geen karakters ) wordt ook na de "b" in de inputstring gevonden, waardoor het volledige pattern matcht met de tekst "b".
De grouping construct in het pattern op regel (4) faalt, maar omdat de backreference optioneel is ( \1? ), maakt het niet uit dat de grouping construct faalt, hierdoor vind ook dit pattern de tekst "b". Visual Basic 2010 Broncode Imports System.Text.RegularExpressions Class Example3 Public Shared Sub Main() Dim input As String = "b" Example3.Print(input, "(a)?b") Example3.Print(input, "(a?)b\1") Example3.Print(input, "(a)?b\1") Example3.Print(input, "(a)?b\1?") input = "ba" Example3.Print(input, "(a)?b\1") Console.ReadLine() End Sub Public Shared Sub Print( ByVal input As String, ByVal pattern As String) Dim r As Regex = New Regex(pattern) Dim m As Match = r.Match(input) If m.Success Then Do Console.WriteLine( """" & input & """ has" & _ " match """ & m.Value & """ " & _ "at index " & m.Index.ToString() & _ " for pattern """ & pattern & """") m = m.NextMatch() Loop While m.Success Else Console.WriteLine( """" & input & """ has no match" & _ " for pattern """ & pattern & """") End If End SubEnd ClassDownload Visual Basic 2010 Broncode Download Visual C# Sourcecode
Console Application Output "b" has match "b" at index 0 for pattern "(a)?b"
"b" has match "b" at index 0 for pattern "(a?)b\1"
"b" has no match for pattern "(a)?b\1"
"b" has match "b" at index 0 for pattern "(a)?b\1?"
"ba" has no match for pattern "(a)?b\1" Je ziet aan pattern (5) hoe beide ( de grouping construct en backreference ) moeten slagen of falen. Als de group faalt, en de backreference slaagt, of vice versa zal er geen match zijn. Uiteraard mogen ze beide enkel falen als het pattern dat toestaat. boven
27.9.2. Optimalisatie via Atomic Grouping ConstructsAls we in een inputstring op zoek gaan naar de tekst "March 1" of "Mar 1" dan kunnen we het pattern (March|Mar) 1 gebruiken (1)(2)(3).
Bij de inputstring op regel (3) wordt er geen match gevonden. Noch de alternatieve subexpressie March, noch subexpressie Mar gevolgd door <space>1 werd gevonden. In dergelijke alternating construct worden de subexpressies van links naar rechts geprobeerd te matchen op de inputstring. Eerst werd de subexpressie March geprobeerd, daarna Mar. Op het moment dat de regex engine besefte dat de subexpressie March wel matchste met de tekst "March", maar de daaropvolgende tekst " 2" niet meer matchste met de subexpressie <space>1, had het eigenlijk geen zin meer om de alternatieve subexpressie Mar gevolgde door <space>1 te proberen matchen. De regex engine heeft reeds geconstateerd dat zich op index 6 van de inputstring geen spatie bevindt, want het matchte op die positie met de "c" uit subexpressie March. Toch zal de regex engine ook deze tweede optie proberen te matchen.
Om deze performance overhead te vermijden, kunnen we gebruik maken van een "atomic grouping construct", deze gebruikt een "nonbacktracking" ( "eager" ) subexpression. Indien een alternatief reeds volledig werd gematcht, maar de daaropvolgende subexpressie niet meer matcht met de inputstring, dan zal er geen backtracking worden toegepast ( wat normaal gezien wel het geval is ), maar de volledige grouping construct falen.
Atomic grouping constructs past men toe door de grouping construct te starten met ?>.
(4) Doordat de subexpressie March volledig matcht met de tekst "March" in de inputstring, maar de daaropvolgende 1 ( <space>1 ) niet matcht, zal de engine niet terugkeren in de inputstring naar index 3, om vanaf daar te proberen te matchen met Mar 1. Wat hier het gewenste effect is, want Mar 1 zal daar toch nooit matchen.
Indien de subexpressie March niet volledig matcht, wordt natuurlijk wel gepoogd om de tweede subexpressie Mar te matchen. Hierdoor levert hetzelfde pattern voor de inputstring "on Mar 1st" wel een match op (5). Visual Basic 2010 Broncode Imports System.Text.RegularExpressions Class Example4 Public Shared Sub Main() Dim pattern As String pattern = "(March|Mar) 1" Example3.Print( "on March 1st", pattern) Example3.Print( "on Mar 1", pattern) Example3.Print( "on March 2nd", pattern) pattern = "(?>March|Mar) 1" Example3.Print( "on March 2nd", pattern) Example3.Print( "on Mar 1st", pattern) pattern = "(?>March|Mar)ch" Example3.Print( "on March 1st", pattern) Console.ReadLine() End SubEnd ClassDownload Visual Basic 2010 Broncode Download Visual C# Sourcecode
Console Application Output "on March 1st" has match "March 1" at index 3 for pattern "(March|Mar) 1"
"on Mar 1" has match "Mar 1" at index 3 for pattern "(March|Mar) 1"
"on March 2nd" has no match for pattern "(March|Mar) 1"
"on March 2nd" has no match for pattern "(?>March|Mar) 1"
"on Mar 1st" has match "Mar 1" at index 3 for pattern "(?>March|Mar) 1"
"on March 1st" has no match for pattern "(?>March|Mar)ch" Let op : Als de atomic group (?>March|Mar) gevolgd wordt door ch, zal dit voor inputstring "on March 1st" geen match opleveren (6). De tekst "March" matchste immers met alternatief March, maar de daarop volgende " 1" matchste niet met ch, de engine keer hier dan ook niet terug naar index 3 in de inputstring om vanaf daar te zoeken naar Mar gevolgd door ch.
Als we op zoek gaan de keywords "For", "Each", "As", "Integer" en "In" en het pattern \b(?>For|Each|As|In|Integer)\b gebruiken, krijgen we geen match voor keyword Integer (1). Keren we de volgorde van de subexpressies In en Integer om, dan bekomen we wel het gewenste effect (2).
Hoewel atomic grouping constructs intressant zijn voor de performantie, dient men dus voldoende aandacht te besteden aan de volgorde waarin de alternatieven zijn gedefinieerd. Visual Basic 2010 Broncode Imports System.Text.RegularExpressions Class Example5 Public Shared Sub Main() Dim input As String = "For Each element As Integer In array1" Dim pattern As String = "\b(?>For|Each|As|In|Integer)\b" Example3.Print(input, pattern) pattern = "\b(?>For|Each|As|Integer|In)\b" Example3.Print(input, pattern) Console.ReadLine() End SubEnd ClassDownload Visual Basic 2010 Broncode Download Visual C# Sourcecode
Console Application Output "For Each element As Integer In array1" has match "For" at index 0 for pattern "
\b(?>For|Each|As|In|Integer)\b"
"For Each element As Integer In array1" has match "Each" at index 4 for pattern
"\b(?>For|Each|As|In|Integer)\b"
"For Each element As Integer In array1" has match "As" at index 17 for pattern "
\b(?>For|Each|As|In|Integer)\b"
"For Each element As Integer In array1" has match "In" at index 28 for pattern "
\b(?>For|Each|As|In|Integer)\b"
"For Each element As Integer In array1" has match "For" at index 0 for pattern "
\b(?>For|Each|As|Integer|In)\b"
"For Each element As Integer In array1" has match "Each" at index 4 for pattern
"\b(?>For|Each|As|Integer|In)\b"
"For Each element As Integer In array1" has match "As" at index 17 for pattern "
\b(?>For|Each|As|Integer|In)\b"
"For Each element As Integer In array1" has match "Integer" at index 20 for patt
ern "\b(?>For|Each|As|Integer|In)\b"
"For Each element As Integer In array1" has match "In" at index 28 for pattern "
\b(?>For|Each|As|Integer|In)\b" boven
27.9.3. Lookaround - Zero-width Assertions"Lookaround" assertions maakt het mogelijk om veronderstellingen te maken over wat zich voor ( "lookbehind" ) of na ( "lookahead" ) bepaalde tokens zou bevinden. boven
27.9.4. Lookahead AssertionsIndien men een veronderstelling wil maken, over wat volgt of wat juist niet volgt na een gevonden match, kan men gebruik maken van een "positive" of "negative lookahead assertions".
Positive lookahead assertion kan via een grouping construct dat start met ?=, negative lookeahead assertions starten met ?!.
Als men elke tekst "the" wil vinden die gevolgd wordt door " brown" kan men de lookahead assertion (?= brown) maken (1). Visual Basic 2010 Broncode Imports System.Text.RegularExpressions Class Example6 Public Shared Sub Main() Dim input As String = "the brown fox in the yellow river" Example3.Print(input, "the(?= brown)") Example3.Print(input, "the(?! brown)") Example3.Print(input, "(the(?= brown)|brown fox)") Example3.Print(input, "(the brown|brown fox)") Console.ReadLine() End SubEnd ClassDownload Visual Basic 2010 Broncode Download Visual C# Sourcecode
Console Application Output "the brown fox in the yellow river" has match "the" at index 0 for pattern "the(
?= brown)"
"the brown fox in the yellow river" has match "the" at index 17 for pattern "the
(?! brown)"
"the brown fox in the yellow river" has match "the" at index 0 for pattern "(the
(?= brown)|brown fox)"
"the brown fox in the yellow river" has match "brown fox" at index 4 for pattern
"(the(?= brown)|brown fox)"
"the brown fox in the yellow river" has match "the brown" at index 0 for pattern
"(the brown|brown fox)" Bemerk de verschillende matches van de patterns op regels (2) en (3). Pattern op regel (2) vind zowel "the" als "brown fox" terug, wat aangeeft dat de substring <space>brown niet werd geconsumeerd. Een lookaround is immers een zero-width assertion. Pattern op regel (3) vindt "the brown" en consumeert deze ook, de cursor van de regex engine staat dan ook na het laatste karakter van deze match. Verder zoekend van op die positie wordt "brown fox" niet meer gevonden.
Er is een verschil tussen een negative lookahead assertion en het werken met een negative character classes.
Zo zoekt in onderstaand voorbeeld het pattern a[^b] niet naar elk tekst waarin a niet gevolgd wordt door een b, maar zoekt het naar elke tekst waarin karakter a gevolgd wordt door een karakter die niet b is (1). Het pattern a[^b] vind dan ook enkel de match "aa".
Als men dan toch zou willen zoeken naar elke tekst waarin a niet gevolgd wordt door een b, kan men gebruik maken van de negative loohahead assertion a(?!b) (2). Deze levert bijvoorbeeld ook een karakter a op het eind van de inputstring op. Visual Basic 2010 Broncode Imports System.Text.RegularExpressions Class Example7 Public Shared Sub Main() Dim input As String = "aa ab a" Example3.Print(input, "a[^b]") Example3.Print(input, "a(?!b)") Console.ReadLine() End SubEnd ClassDownload Visual Basic 2010 Broncode Download Visual C# Sourcecode
Console Application Output "aa ab a" has match "aa" at index 0 for pattern "a[^b]"
"aa ab a" has match "a" at index 0 for pattern "a(?!b)"
"aa ab a" has match "a" at index 1 for pattern "a(?!b)"
"aa ab a" has match "a" at index 6 for pattern "a(?!b)" Bemerkt hoe de negative lookahead assertion a(?!b) zowel de "a" op index 0, als de "a" op index 1 vindt. Nogmaals, lookaround assertions zijn zero-width assertions en consumeren geen karakters. Nadat de eerste "a" op index 0 matcht met subexpressie a en de daaropvolgende tekst "a" op index 1 ook matcht met subexpressie (?!b), wordt ondanks dat de cursor van de engine zich bevindt na "a" op index 1, na deze eerste totale match, verder gezocht naar matches vanaf index 1.
Stel dat we zoeken via pattern a(?=b)c in de inputstring "abc ac" dan wordt geen enkele match gevonden (1). Visual Basic 2010 Broncode Imports System.Text.RegularExpressions Class Example8 Public Shared Sub Main() Example3.Print( "abc ac", "a(?=b)c") Console.ReadLine() End SubEnd ClassDownload Visual Basic 2010 Broncode Download Visual C# Sourcecode
Console Application Output "abc ac" has no match for pattern "a(?=b)c" Bovenstaand pattern zal nooit matches opleveren gezien verondersteld wordt dat volgende op een tekst "a" een "b" moet staan, maar ook een "c" moet staan. boven
27.9.5. Lookbehind AssertionsHet is via "lookbehind assertions" ook mogelijk een veronderstelling te maken over hetgeen zich voor een bepaalde subexpressie moet bevinden.
De grouping construct moet in dat geval starten met ?< gevolgd door een = voor positive en een ! voor negative lookbehind assertions. Visual Basic 2010 Broncode Imports System.Text.RegularExpressions Class Example9 Public Shared Sub Main() Example3.Print( "ab", "(?<=a)b") Example3.Print( "ab", "(?<!a)b") Console.ReadLine() End SubEnd ClassDownload Visual Basic 2010 Broncode Download Visual C# Sourcecode
Console Application Output "ab" has match "b" at index 1 for pattern "(?<=a)b"
"ab" has no match for pattern "(?<!a)b" In de regex engine van .NET kan elke subexpressie gebruikt worden in een lookaround ( lookbehind of lookahead ). Dit kan zelf een grouping construct zijn, bijvoorbeeld (?=(...)) of een character class of een alternation construct of ... . Dit is in lang niet alle andere engines het geval, tot op moment van schrijven beschikt enkel de JGsoft engine over dezelfde capaciteit. De meeste andere engines hebben of geen support voor lookbehind, of een beperkte support ( waar men beperkt is in de mogelijke subexpressies van een lookbehind ).
Belangrijk nog is dat lookaround grouping construct noncapturing groups zijn, men kan bijgevolg geen backreference gebruiken naar de lookaround. Als je een andere grouping construct gebruikt als subexpressie voor de lookaround, dan is het wel mogelijk om verderop naar deze te verwijzen. In het pattern (?=(<group1>...))...\{group1}... zal de backreference \{group1} verwijzen naar de captures van group1. boven
27.9.6. Conditional Alternation ConstructsHet is mogelijk om een conditie te koppelen aan een bepaalde subexpressie, dit om aan te geven dat enkel naar een match van deze subexpressie wordt gezocht indien voldaan is aan deze conditie.
De synthax is (?(condition)then|else), het |else gedeelte is optioneel. De haakjes rond deze alternation construct, noch deze rond de conditie zelf, maken hiervan capturing groups. Men kan hier bijvoorbeeld via een backreference niet naartoe verwijzen.
Voor de conditie zijn er 2 mogelijkheden, een "expression test" (1)(2) en een "capture test" (3).
Een expression test is altijd een lookaround grouping construct ( zero-width assertion en noncapturing ), by default is dit een positive lookahead assertions. De (condition) is dus by default gelijk aan (?=condition). Het type van lookaround ( positive of negative en lookbehind of lookahead ) kan worden aangepast.
Een capture test is een verwijzing naar een capturing group, aan de hand van een naam van een named group of gewoon een nummer van capturing group. In tegenstelling tot een gewone backreference wordt hier geen backslash gebruikt.
Het then en else gedeelte kan bestaan uit eender welke subexpressie, dit kan bijvoorbeeld een alternation construct zijn, plaats in dat geval haakjes rond de verschillende alternatieven, om verwarring tussen de vertical bar operators te voorkomen. Bijvoorbeeld (?(condition)then|(else1|else2)).
In onderstaand voorbeeld gaat het pattern op regel (1) op zoek naar woorden van 8 karakters die eindigen op "book" of naar het woord "the". Pattern op regel (2) gaat op zoek naar "Basic" voorafgegaan door de tekst "Visual " of naar "C#" of "J#". Pattern (5)?6(?(1)7|8) op regel (3) verwijst in zijn conditie (1) naar de capturing group (5). Er wordt gezocht naar teksten "567", of teksten die niet starten met "5", wel met "6" en daaropvolgend een "8". Visual Basic 2010 Broncode Imports System.Text.RegularExpressions Class Example10 Public Shared Sub Main() Dim input As String Dim pattern As String input = "this is the textbook" pattern = "(?(\b\w{8}\b)\w{4}book|the)" Example3.Print(input, pattern) input = "in Visual Basic and C#" pattern = "((?<=Visual )Basic|(C|J)#)" Example3.Print(input, pattern) input = "567 67 68 568" pattern = "(5)?6(?(1)7|8)" Example3.Print(input, pattern) Console.ReadLine() End SubEnd ClassDownload Visual Basic 2010 Broncode Download Visual C# Sourcecode
Console Application Output "this is the textbook" has match "the" at index 8 for pattern "(?(\b\w{8}\b)\w{4
}book|the)"
"this is the textbook" has match "textbook" at index 12 for pattern "(?(\b\w{8}\
b)\w{4}book|the)"
"in Visual Basic and C#" has match "Basic" at index 10 for pattern "((?<=Visual
)Basic|(C|J)#)"
"in Visual Basic and C#" has match "C#" at index 20 for pattern "((?<=Visual )Ba
sic|(C|J)#)"
"567 67 68 568" has match "567" at index 0 for pattern "(5)?6(?(1)7|8)"
"567 67 68 568" has match "68" at index 7 for pattern "(5)?6(?(1)7|8)"
"567 67 68 568" has match "68" at index 11 for pattern "(5)?6(?(1)7|8)"
Dit artikel is gepubliceerd op zondag 31 juli 2011 op vbvoorbeelden, bezoek de website voor een recente versie van dit artikel of andere artikels.
|