Regex, grupper och backreferences

Permalänk
Hedersmedlem

Regex, grupper och backreferences

Jag håller på och gör en liten wiki (baserad på denna tutorial) för att lära mig Ruby on Rails.
Det som den tar upp är jag klar med för länge sen, nu är jag ute på okänt vatten och meckar själv.

Jag vill lägga till stöd för att ha både länkar i stil med [[Detta]] och CamelCase-länkar, men jag har snubblat på regex-biten. Detta har jag nu:

@page.content.gsub!(/ # [[Name]]-style (?: \[\[ ([^\]]*) \]\] ) | # CamelCase ( (?: [A-Z]\w+ ) {2,}) /x, '<a href="/wiki/\1\2">(match 1: \1) (match 2: \2)</a>')

Koden ovan funkar fint (med lite debug-output eller vad man ska kalla det), men som ni ser så kommer [[länkarna]] som \1 och CamelCase som \2... Jag vill att båda ska matcha som \1, så att jag inte behöver köra fullösningen att skriva ut \1\2. Eftersom samma text inte kan matcha båda så borde det ju gå, men jag har ändrat grupperna hit och dit utan att lyckas... Hur gör man?

Visa signatur

Asus ROG STRIX B550-F / Ryzen 5800X3D / 48 GB 3200 MHz CL14 / Asus TUF 3080 OC / WD SN850 1 TB, Kingston NV1 2 TB + NAS / Corsair RM650x V3 / Acer XB271HU (1440p165) / LG C1 55"
NAS: 6700K/16GB/Debian+ZFS | Backup (offsite): 9600K/16GB/Debian+ZFS

Permalänk
Glömsk

Testa named capturing groups med samma namn på båda grupperna. Tror inte det funkar dock, inte en enda regexmotor jag testat stödjer detta och ruby använder väl PCRE? Så då är det kört.

Tycker faktiskt inte det är något fel med lösningen du använder nu, är så bra det kan bli när en matchning innehåller tecken (hakarna i detta fallet) som inte ska vara med. Utan hakarna, eller om camelcaselänkarna hade hakar, vore problemet lättare.

Visa signatur

...man is not free unless government is limited. There's a clear cause and effect here that is as neat and predictable as a law of physics: As government expands, liberty contracts.

Permalänk

/ ( # [[Name]] \[\[ ([^\]]+) \]\] | # CamelCase ([A-Z]\w+) {2,} ) /x

Otestad, och jag har ingen aning om vad (?: innebär så jag utelämnade dem.

Permalänk
Hedersmedlem
Citat:

Ursprungligen inskrivet av vigge89

Otestad, och jag har ingen aning om vad (?: innebär så jag utelämnade dem.

"(?:regex) Non-capturing parentheses group the regex so you can apply regex operators, but do not capture anything and do not create backreferences."

Det fungerade tyvärr inte, men nästan. Den matchar hela [[Länkar]], så den skriver ut t ex

<a href="/wiki/[[Länk]]">[[Länk]]</a>

Tror att man måste använda (?: för att komma förbi det, om man nu inte kan undvika att gruppera det helt på nåt vis.

Citat:

Ursprungligen inskrivet av Psionicist

Tycker faktiskt inte det är något fel med lösningen du använder nu, är så bra det kan bli när en matchning innehåller tecken (hakarna i detta fallet) som inte ska vara med. Utan hakarna, eller om camelcaselänkarna hade hakar, vore problemet lättare.

Menar du det jag skrev ovan?

Visa signatur

Asus ROG STRIX B550-F / Ryzen 5800X3D / 48 GB 3200 MHz CL14 / Asus TUF 3080 OC / WD SN850 1 TB, Kingston NV1 2 TB + NAS / Corsair RM650x V3 / Acer XB271HU (1440p165) / LG C1 55"
NAS: 6700K/16GB/Debian+ZFS | Backup (offsite): 9600K/16GB/Debian+ZFS

Permalänk

Aha, (?:...) är alltså en variant av atomic grouping, jag har endast stött på (?>...) tidigare.
Jag kan för närvarande inte komma på något enkelt sätt att kombinera de båda varianterna i ett uttryck utan att det krävs att man använder flera backreferences =/ Personligen tycker jag att du antingen bör behålla din ursprungliga lösning eller delar upp dem i varsitt uttryck, om ingen annan har ett uttryck på G som inte är onödigt överkomplicerar det hela.

Permalänk
Hedersmedlem

Först hade jag två olika, men det blev lite strul (åtminstone när CamelCase kördes sist, då den pajade länkarna som genererats innan).
Det lär jag kunna komma runt ganska enkelt dock, men nu ska jag sova...

Halvt offt så har inte Ruby stöd för look-behind (förrän 1.9 som inte "finns" än), störande.

Visa signatur

Asus ROG STRIX B550-F / Ryzen 5800X3D / 48 GB 3200 MHz CL14 / Asus TUF 3080 OC / WD SN850 1 TB, Kingston NV1 2 TB + NAS / Corsair RM650x V3 / Acer XB271HU (1440p165) / LG C1 55"
NAS: 6700K/16GB/Debian+ZFS | Backup (offsite): 9600K/16GB/Debian+ZFS

Permalänk
Medlem

Eftersom man kan använda resultatet från ett block som ersättningstext så kan du fånga hela det första regexet och sen plocka bort [[]] i efterhand.

@page.content.gsub!(/ ( # [[Name]]-style \[\[ [^\]]* \]\] | # CamelCase (?: [A-Z]\w+ ){2,} ) /x) { |link| link.gsub!(/\[\[ ([^\]]*) \]\]/x, '\1') %Q{<a href="/wiki/#{link}">#{link}</a>} }

Permalänk
Glömsk
Citat:

Ursprungligen inskrivet av Kimtaro
Eftersom man kan använda resultatet från ett block som ersättningstext

Oj, det var snyggt! 1 poäng till Ruby för den möjligheten.

Visa signatur

...man is not free unless government is limited. There's a clear cause and effect here that is as neat and predictable as a law of physics: As government expands, liberty contracts.

Permalänk

Jo men då använder man ju två uttryck i alla fall

Permalänk
Medlem
Citat:

Ursprungligen inskrivet av vigge89
Jo men då använder man ju två uttryck i alla fall

Vill man nödvändigtvis inte ha två regexp så kan man använda blocket för att göra en variant på OP:s första kod, men som i mitt tyckte blir tydligare och lättare att läsa.

@page.content.gsub!(/ (?: # [[Name]]-style \[\[ ([^\]]*) \]\] | # CamelCase ( (?: [A-Z]\w+ ){2,} ) ) /x) { link = $1 ? $1 : $2 %Q{<a href="/wiki/#{link}">#{link}</a>} }

Permalänk
Medlem

Alltså... har inte typ alla ni skrivit fel i era CamelCase satser?

( (?: [A-Z]\w+ ) {2,})

blir ju inte CamelCase liksom pga whitespace

((?:[A-Z]\w+){2,})

däremot...

Jag skulle nog delat upp det i två regular expressions ändå pga höjd läsbarhet... såhär kan man göra i python, och det ser typ identiskt ut i ruby...

>>> text = 'I like to [[poop]]. Pooping is a fun [[thing]] to do. BananaMonkey is [[nice]]' >>> def do_magic_stuff(data): ... data = re.sub(r'\[\[([^\]]+)\]\]', r'<a href="\1">\1</a>', data) # [[links]] ... data = re.sub(r'((?:[A-Z]\w+){2,})', r'<a href="\1">\1</a>', data) # CamelCaseLinks ... return data ... >>> do_magic_stuff(text) 'I like to <a href="poop">poop</a>. Pooping is a fun <a href="thing">thing</a> to do. <a href="BananaMonkey">BananaMonkey</a> is <a href="nice">nice</a>'

(varför blir det inte monospace fonts i code-taggar på sweclockers ibland? Fixa!)

Permalänk

http://www.regular-expressions.info/modifiers.html

Citat:

# /x enables "free-spacing mode". In this mode, whitespace between regex tokens is ignored, and an unescaped # starts a comment.

Permalänk
Medlem
Citat:

Ursprungligen inskrivet av Buffi
Jag skulle nog delat upp det i två regular expressions ändå pga höjd läsbarhet... såhär kan man göra i python, och det ser typ identiskt ut i ruby...

Då får du problem om en användare skriver CamelCase i en bracket-länk:

>>> import re >>> text = '[[CamelCaseInBrackets]]' >>> def find_links(data): ... data = re.sub(r'\[\[([^\]]+)\]\]', r'<a href="\1">\1</a>', data) # [[links]] ... data = re.sub(r'((?:[A-Z]\w+){2,})', r'<a href="\1">\1</a>', data) # CamelCaseLinks ... return data ... >>> find_links(text) '<a href="<a href="CamelCaseInBrackets">CamelCaseInBrackets</a>"><a href="CamelCaseInBrackets">CamelCaseInBrackets</a></a>'

Det går ju att lösa i efterhand genom att kolla så den inte har dubbellänkat något. Men det verkar som en onödigt omständig lösning.

Så jag vill nog hävda att ett regexp med ett block är den bästa lösningen.

Tillägg:

Ett till problem med att ha två regexp är att du har ersättningstexten på två ställen, vilket kan ge upphov till buggar i framtiden om man bara uppdaterar den ena men glömmer den andra.

En till fördel med block är att man kan skicka texten till en extern template-motor för rendering, så man separerar länktexten från koden.