Is there an easy way to count the number of occurrences of wildcards in a QString object?
-
Actually, I would like to know the DISTINCT count of wildcards (
%1
,%2
, etc.) in a string passed as a parameter to a function which does the replacements.I couldn't find anything in the API documentation for QString for doing this. I could write it myself using the
QString::count()
function repeatedly, but it seems like something that should be built into the API. -
Actually, I would like to know the DISTINCT count of wildcards (
%1
,%2
, etc.) in a string passed as a parameter to a function which does the replacements.I couldn't find anything in the API documentation for QString for doing this. I could write it myself using the
QString::count()
function repeatedly, but it seems like something that should be built into the API.@Robert-Hairgrove said in Is there an easy way to count the number of occurrences of wildcards in a QString object?:
but it seems like something that should be built into the API.
Why? Where should this be needed?
-
Actually, I would like to know the DISTINCT count of wildcards (
%1
,%2
, etc.) in a string passed as a parameter to a function which does the replacements.I couldn't find anything in the API documentation for QString for doing this. I could write it myself using the
QString::count()
function repeatedly, but it seems like something that should be built into the API.@Robert-Hairgrove said in Is there an easy way to count the number of occurrences of wildcards in a QString object?:
DISTINCT
count of wildcards (%1, %2, etc.)Since you pointedly say
DISTINCT
(I presume your string can contain e.g.%2
multiple times) you cannot just search for%
s. You will want toindexOf()
the string to find each%
and act on the next character. I wouldn't callQString::count()
9 times if it were me, but up to you. -
@Christian-Ehrlicher and @JonB : Thank you for the replies. I assume this means there is no built-in function, so I will try using a regular expression and count the matches.
So far, this seems to work well enough as a pattern to capture any combination of 1 or 2 digits preceded by a single
%
character. If there are more than three digits following the token, only the first two are matched (QString::arg()
can handle between 1 and 99 distinct wildcards according to the documentation, but I will certainly not need that many):(^[^%]?%\d{1,2})|([^%]%\d{1,2})
Of course, the backslashes will need to be escaped, but it seems to work OK when tested on the site https://regex101.com. I can eliminate duplicates when I iterate over the matches.
Can anyone come up with an example where this might break?
-
@Christian-Ehrlicher and @JonB : Thank you for the replies. I assume this means there is no built-in function, so I will try using a regular expression and count the matches.
So far, this seems to work well enough as a pattern to capture any combination of 1 or 2 digits preceded by a single
%
character. If there are more than three digits following the token, only the first two are matched (QString::arg()
can handle between 1 and 99 distinct wildcards according to the documentation, but I will certainly not need that many):(^[^%]?%\d{1,2})|([^%]%\d{1,2})
Of course, the backslashes will need to be escaped, but it seems to work OK when tested on the site https://regex101.com. I can eliminate duplicates when I iterate over the matches.
Can anyone come up with an example where this might break?
@Robert-Hairgrove
Personally like I wrote I think I would scan withindexOf()
, seems simpler to me than reg ex. May be personal preference!I think yours will go wrong on
%%%1
etc.? Which is why I think it's trickier with a reg ex than your own code? -
@Robert-Hairgrove
Personally like I wrote I think I would scan withindexOf()
, seems simpler to me than reg ex. May be personal preference!I think yours will go wrong on
%%%1
etc.? Which is why I think it's trickier with a reg ex than your own code?@JonB No, I tried this on the regex101 site. It was indeed a little tricky to get that part right, but here is the breakdown of the pattern:
1st capturing group: Looks at the beginning of the string for any character not equal to
%
, or none, followed by exactly one%
followed by 1 or 2 digits;2nd (alternate) capture group: Looks for a sequence which MUST begin with a non-
%
character followed by exactly one%
followed by 1 or 2 digits.If the string begins with
"%1"
it will match the first group, but"%%1"
does not match anywhere. Anything past the beginning of the string must match the second capture group, and this requires a%
preceded by something else and followed by 1 or 2 digits, whereas the non-%
character at the beginning of the string is optional (due to the"?"
token). -
@JonB No, I tried this on the regex101 site. It was indeed a little tricky to get that part right, but here is the breakdown of the pattern:
1st capturing group: Looks at the beginning of the string for any character not equal to
%
, or none, followed by exactly one%
followed by 1 or 2 digits;2nd (alternate) capture group: Looks for a sequence which MUST begin with a non-
%
character followed by exactly one%
followed by 1 or 2 digits.If the string begins with
"%1"
it will match the first group, but"%%1"
does not match anywhere. Anything past the beginning of the string must match the second capture group, and this requires a%
preceded by something else and followed by 1 or 2 digits, whereas the non-%
character at the beginning of the string is optional (due to the"?"
token).@Robert-Hairgrove
I don't understand what you are saying. I suggested what will not work:%%%1
somewhere/anywhere (beginning/middle/end). That is 3 percent characters. It expands to a literal
%
followed by your%1
substitution. In my head your reg ex will fail to recognise the%1
because it will see it as a%%1
and so reject the match. Effectively you do not deal with%%
s correctly. -
@Robert-Hairgrove
I don't understand what you are saying. I suggested what will not work:%%%1
somewhere/anywhere (beginning/middle/end). That is 3 percent characters. It expands to a literal
%
followed by your%1
substitution. In my head your reg ex will fail to recognise the%1
because it will see it as a%%1
and so reject the match. Effectively you do not deal with%%
s correctly.@JonB Hmmm ... I think you might be right about this. I was probably confusing the SQL
LIKE
syntax where"%"
is also used as a wildcard, and to search for a literal"%"
string, one must write it twice, i.e.:WHERE something LIKE '%%1'
would match the string'%1'
andWHERE something LIKE '%1'
would match'a1'
,'b1'
etc. So I was deliberately trying NOT to match the wildcard if it had two or more%
tokens.In that case, the pattern would be much simpler:
"%\d{1,2}"
which would match anywhere in the string. -
@JonB Hmmm ... I think you might be right about this. I was probably confusing the SQL
LIKE
syntax where"%"
is also used as a wildcard, and to search for a literal"%"
string, one must write it twice, i.e.:WHERE something LIKE '%%1'
would match the string'%1'
andWHERE something LIKE '%1'
would match'a1'
,'b1'
etc. So I was deliberately trying NOT to match the wildcard if it had two or more%
tokens.In that case, the pattern would be much simpler:
"%\d{1,2}"
which would match anywhere in the string.@Robert-Hairgrove
Sorry, but again I think you are misunderstanding.(Oh, btw, it may not be documented, but I believe Qt does do
%%
=>%
for arguments, and I have used that.)If you went for
%\d{1,2}
that would indeed incorrectly cause%%1
to be treated as%1
substitution. You are correct that you do need to deal with%1
versus%%1
, as you tried earlier. But I believe you will go wrong on%%%1
, you will see it as%%1
and thereby not thing it is a%1
but it is!Try
abc %1 %%2 %%%3 %%%%4 def
with whatever reg ex you choose. I have said I would probably not use reg exs here for this reason, I would just do it viaindexOf('%', ...)
and deal with the following characters (%
, digits, something else) myself. But if you want to use reg ex here you'll have to deal with this issue! -
I still don't understand the use-case for this though...
If you want to see what Qt is doing search for findArgEscapes() in qstring.cpp -
I still don't understand the use-case for this though...
If you want to see what Qt is doing search for findArgEscapes() in qstring.cpp@Christian-Ehrlicher The use case would be like this: A function receives a pattern (a string with some wildcards) from some other place and needs to fill in the wildcards within the function, returning the completed string. My function would like to assert that the input has the expected number of wildcards before trying to do replacements.
-
@Robert-Hairgrove
Sorry, but again I think you are misunderstanding.(Oh, btw, it may not be documented, but I believe Qt does do
%%
=>%
for arguments, and I have used that.)If you went for
%\d{1,2}
that would indeed incorrectly cause%%1
to be treated as%1
substitution. You are correct that you do need to deal with%1
versus%%1
, as you tried earlier. But I believe you will go wrong on%%%1
, you will see it as%%1
and thereby not thing it is a%1
but it is!Try
abc %1 %%2 %%%3 %%%%4 def
with whatever reg ex you choose. I have said I would probably not use reg exs here for this reason, I would just do it viaindexOf('%', ...)
and deal with the following characters (%
, digits, something else) myself. But if you want to use reg ex here you'll have to deal with this issue!@JonB I looked at the source code, as @Christian-Ehrlicher suggested, and it appears that
QString("%%%1").arg("xyz")
would result in the string"%%xyz"
, although I didn't try it yet. -
@JonB I looked at the source code, as @Christian-Ehrlicher suggested, and it appears that
QString("%%%1").arg("xyz")
would result in the string"%%xyz"
, although I didn't try it yet.QString("%%%1").arg("xyz")
would result in the string"%%xyz"
I would expect it to produce
%xyz
. But in any case the issue is I expect your code to miss the%1
in this case. -
QString("%%%1").arg("xyz")
would result in the string"%%xyz"
I would expect it to produce
%xyz
. But in any case the issue is I expect your code to miss the%1
in this case.@JonB Finally got around to setting up a little test app (with Qt 5.15.5 on Linux Ubuntu 18.04).
This is what it prints:Original: Testing %%%1 Result: Testing %%xyz
main.cpp
#include <QString> #include <string> #include <iostream> const QString s("Testing %%%1"); int main() { const QString rep("xyz"); const QString res = s.arg(rep); std::cout << "Original: \t" << s.toStdString() << "\nResult: \t" << res.toStdString() << std::endl; return 0; }
.pro file:
QT -= gui CONFIG += c++11 console CONFIG -= app_bundle # You can make your code fail to compile if it uses deprecated APIs. # In order to do so, uncomment the following line. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 SOURCES += \ main.cpp # Default rules for deployment. qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target
-
Changed the regexp now to this:
%[1-9][0-9]?
Just in case someone writes leading zeroes, i.e.
"ABC %01 XYZ"
instead of"ABC %1 XYZ"
. The question mark after the second brackets group is necessary for matching either single digits or two-digit numbers.