Find a substring in block of text, unless it is part of another substring
<p>I was looking for an efficient way to find a substring between two expressions, unless the expression is a part of another.</p>

<p>For example:</p>

<blockquote>
  <p>Once upon a time, in a time far far away, dogs ruled the world. The End.</p>
</blockquote>

<p>If I was searching for the substring between <strong>time</strong> and <strong>end</strong>, I would receive:</p>

<blockquote>
  <p>in a time far far away, dogs ruled the world. The</p>
</blockquote>

<p>or </p>

<blockquote>
  <p>far far away, dogs ruled the world. The</p>
</blockquote>

<p>I want to ignore if <strong>time</strong> is a part of <strong>Once upon a time</strong>. I didn't know if there was a pythonic method without using crazy for loops and if/else cases.</p>

<p>The typical solution here is to use capturing and non-capturing regular expression groups. Since regex alternations get parsed from left to right, placing any <em>exceptions</em> to the rule first (as a non-capture) and end with the alternation that you want to select for.</p>

<pre><code>import re

text = "Once upon a time, in a time far far away, dogs ruled the world. The End."
query = re.compile(r"""
  Once upon a time|            # literally 'Once upon a time',
                               # should not be selected
  time\b                       # from the word 'time'
  (.*)                         # capture everything
  \bend                        # until the word 'end'
""", re.X | re.I)

result = query.findall(text)
# result = ['', ' far far away, dogs ruled the world. The ']
</code></pre>

<p>You can strip out the empty group (that got put in when we matched the unwanted string)</p>

<pre><code>result = list(filter(None, result))
# or result = [r for r in result if r]
# [' far far away, dogs ruled the world. The ']
</code></pre>

<p>and then strip the results</p>

<pre><code>result = list(map(str.strip, filter(None, result)))
# or result = [r.strip() for r in result if r]
# ['far far away, dogs ruled the world. The']
</code></pre>

<p>This solution is particularly useful when you have a number of phrases you're trying to dodge.</p>

<pre><code>phrases = ["Once upon a time", "No time like the present", "Time to die", "All we have left is time"]
querystring = r"time\b(.*)\bend"
query = re.compile("|".join(map(re.escape, phrases)) + "|" + querystring, re.I)

result = [r.strip() for r in query.findall(some_text) if r]
</code></pre>

python python-2.7