4

Problem

I use QGIS with the expression function regexp_matches() that returns an array of all strings captured by capturing groups. Regular expressions normally capture only the first match (occurence) in a string. So for example the following expression:

regexp_matches('These are my QGIS strings!','(s.)')

returns only [ 'se' ], as expected: the first occurence of s (position 4 of the input string), followed by the next character.

Question

How to rewrite the expression to capture all occurences of s, followed by the next character? The result I want would look like: [ 'se', 'st', 's!' ]

What I tried

Using the regexp search pattern '(s.).*(s.).*(s.)' would be a workaround. It's not very elegat as you already have to know how often s occurst. There seems to be a /g modifier in regular expressions for global matching, however, I was not able to find out the correct syntax.

2 Answers 2

7

I believe the global matching flag is not (yet) supported, but there is a workaround.

regexp_replace comes to the rescue as it will replace any occurrence of the pattern. The key trick is that everything must be found in the various matching groups. We will make it remove what does not correspond to the initial pattern and we will add a separator.

regexp_replace('These are my QGIS strings!',
               '([^s])*(s.)([^s])*',
               '\\2;')

==> 'se;st;s!;'

Let's break it down: Anything between parenthesis is a capture group, which is numbered starting at 1.

([^s])*: Anything that is not an s, as many as possible (*)
(s.): Followed by the desired target, an s and its next character
([^s])*: Optionally followed by anything that is not an s, as many as possible
\\2;: replace by the 2nd capture group (the s.) followed by ;. This will be repeated for each occurence of the 2nd capture group :-)

The next step is to break this string into an array

string_to_array(
   regexp_replace('These are my QGIS strings!',
                  '([^s])*(s.)([^s])*',
                  '\\2;'),
delimiter:=';')

==> ['se','st','s!','']

And at last, since the last ; is creating an empty row in the array, we need to remove the last row:

array_remove_at(
   string_to_array(
     regexp_replace('These are my QGIS strings!',
                    '([^s])*(s.)([^s])*',
                    '\\2;'),
   delimiter:=';'),
-1)

==> ['se','st','s!']
6

Unfortunately there is a bug(?) in regexp_matches() function. It returns only first occurence of provided pattern.

regexp_matches( 'QGIS is the best GIS solution in the universe!', '(s.)') returns array, but with only 1 element: [ 's ' ]

There is a workaround. You can write your own function! You have to go to Function editor tab, right to the Expression tab (in expression editor, i.e. in field calculator). Then click on + button to add a new file. in this new Python file you will define your own expression functions. This is my code to brand new regexp_matches_array function which will appear in the new group _my-own-functions in expression editor:

from qgis.core import *  
from qgis.gui import *  
import re  

@qgsfunction(group='_my-own-functions', usesgeometry=False)  
def regexp_matches_array( input, pattern):  
    return re.findall(pattern, input)

regexp_matches_array( 'QGIS is the best GIS solution in the universe!', '(s.)') returns an array of all occurencies of our pattern: [ 's ', 'st', 'so', 'se' ]

If you want to learn more about this then here is a really nice tutorial: https://www.qgistutorials.com/en/docs/3/custom_python_functions.html

3
  • I don't think it's a bug that only first match is returned, this is default behaviour with regular expressions.
    – Babel
    Commented Nov 12, 2023 at 19:18
  • 1
    I think the names of this functions in QGIS are some kind tricky. My first idea when I see regexp_matches() in Arrays group and regexp_match() in String group is that it will do the same, but matches would return array of all the matches in a string. And it does return many elements... but only if you have more groups defined in your pattern. If you have one group in your pattern it will return only first occurence in a string as an array.
    – rychlik
    Commented Nov 12, 2023 at 20:26
  • 4
    Neat solution! Regarding the name/bug, Postgres has a flag attribute in regexp_matches to return the 1st or all matches. I guess this feature is missing in QGIS
    – JGH
    Commented Nov 12, 2023 at 21:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.