Filter elements with a regex, only if they are in a certain block

Viewed 54

In a string (in reality it's much bigger):

s = """
BeginA
Qwerty
Element 11 35
EndA

BeginB
Element 12 38
...
Element 198 38
EndB

BeginA
Element 81132 38
SomethingElse
EndA

BeginB
Element 12 39
Element 198 38
EndB
"""

how to replace every Element <anythinghere> 38 which is inside a BeginB...EndB block (and only those!) by Element ABC?

I was trying with:

s = re.sub(r'Element .* 38', 'Element ABC', s)

but this doesn't detect if it's in a BeginB...EndB block. How to do this?

2 Answers

Try the following:

r'(?s)(?<=BeginB)\s+Element\s+(\d+)\s+\d+.*?(?=EndB)'

You can test it here.

For your example, I would echo @Jan's answer and use two separate regular expressions:

import re

restrict = re.compile(r'(?s)(?<=BeginB).*?(?=EndB)')
pattern = re.compile(r'Element\s+(\d+)\s+38')

def repl(block):

    return pattern.sub('Element ABC', block.group(0))

out = restrict.sub(repl, s)

Use two expressions:

block = re.compile(r'BeginB[\s\S]+?EndB')
element = re.compile(r'Element.*?\b38\b')

def repl(match):
    return element.sub('Element ABC', match.group(0))

nstring = block.sub(repl, string)
print(nstring)

This yields

BeginA
Qwerty
Element 11 35
EndA

BeginB
Element ABC
...
Element ABC
EndB

BeginA
Element 81132 38
SomethingElse
EndA

BeginB
Element 12 39
Element ABC
EndB

See a demo on ideone.com.


Without re.compile (just to get the idea):

def repl(match):
    return re.sub(r'Element.*?\b38\b', 'Element ABC', match.group(0))

print re.sub(r'BeginB[\s\S]+?EndB', repl, s)

The important idea here is the fact that re.sub's second parameter can be a function.


You could very well do it without a function but you'd need the newer regex module which supports \G and \K:

rx = re.compile(r'''
    (?:\G(?!\A)|BeginB)
    (?:(?!EndB)[\s\S])+?\K
    Element.+?\b38\b''', re.VERBOSE)

string = rx.sub('Element ABC', string)
print(string)

See another demo for this one on regex101.com as well.

Related