Help with regexp

66 views
Skip to first unread message

Andrew Poulos

unread,
Aug 6, 2021, 3:22:48 AMAug 6
to
I have a string that looks like this

let str = 'linear-gradient(90deg, #ff0000 50%, #00ff00 100%)"

I need to convert the hex values to rgba so I've written a function

const convertHex2Rgba = (hex, alpha = 1) => {
...
return `rgba(${r},${g},${b},${alpha})`;
};

I'm stuck trying to write a regexp that replaces the hex values (that
may have either 3 or 6 characters - plus the #) with the value returned
from convertHex2Rgba.

This (one of my many failed attempts):

str.replace(/#[0-9a-fA-F]/, convertHex2Rgba('$1'));

does nothing.

Andrew Poulos

Andrew Poulos

unread,
Aug 6, 2021, 3:41:35 AMAug 6
to
This has got me a bit closer

str.replace(/#([A-Fa-f0-9]{3}){1,2}/, convertHex2Rgba('$1'));

but the match doesn't get replaced.

Andrew Poulos

Andrew Poulos

unread,
Aug 6, 2021, 3:56:02 AMAug 6
to
str.replace(/#([A-Fa-f0-9]{3}){1,2}/, match => {
return convertHex2Rgba(match);
});

Nearly there, but I have to do it one match at a time.

Andrew Poulos


Julio Di Egidio

unread,
Aug 6, 2021, 4:39:10 AMAug 6
to
Your capturing group in "([A-Fa-f0-9]{3})" which only matches "#" followed by three hex digits: the "{1,2}" won't change that, it means repeated occurrences of that same pattern.

> Nearly there, but I have to do it one match at a time.

Because you are missing the 'g' flag, for "global" search and substitution. I have come up with this one, which is as simple as i could make it (while staying correct):

/(#[A-Fa-f0-9]{6}|#[A-Fa-f0-9]{3})/gs

Have a look at the docs and the examples:
<https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#using_an_inline_function_that_modifies_the_matched_characters>

Also, you can prototype with online tools, e.g. as I have done here: <https://regex101.com/r/BdPOyA/1> (you cannot use a replacer function there, but at least you know your pattern is correct).

HTH,

Julio

Julio Di Egidio

unread,
Aug 6, 2021, 4:53:28 AMAug 6
to
On Friday, 6 August 2021 at 10:39:10 UTC+2, Julio Di Egidio wrote:
> On Friday, 6 August 2021 at 09:56:02 UTC+2, Andrew Poulos wrote:
> > On 6/08/2021 5:41 pm, Andrew Poulos wrote:

> > > but the match doesn't get replaced.
> > str.replace(/#([A-Fa-f0-9]{3}){1,2}/, match => {
> > return convertHex2Rgba(match);
> > });
>
> Your capturing group in "([A-Fa-f0-9]{3})" which only matches "#" followed by three hex digits: the "{1,2}" won't change that, it means repeated occurrences of that same pattern.

No, I have misread that, for one thing "#" is out of the match... Never mind, that is why I rather prototype. ;)

Julio

Jon Ribbens

unread,
Aug 6, 2021, 6:36:57 AMAug 6
to
You need the /g flag to tell it to do more than one match.
You could also use the /i flag to be case-insensitive:

str.replace(/#([0-9a-f]{3}){1,2}/gi, match => convertHex2Rgba(match))

You might also want to make sure that you're not seeing *more* than
6 hexadecimal characters:

/#([0-9a-f]{3}){1,2}(?![0-9a-f])/gi

The Natural Philosopher

unread,
Aug 6, 2021, 2:10:20 PMAug 6
to
On 06/08/2021 08:22, Andrew Poulos wrote:
> I have a string that looks like this
>
> let str = 'linear-gradient(90deg, #ff0000 50%, #00ff00 100%)"
>
> I need to convert the hex values to rgba so I've written a function
>
> const convertHex2Rgba = (hex, alpha = 1) => {
>   ...
>   return `rgba(${r},${g},${b},${alpha})`;
> };
>
> I'm stuck trying to write a regexp that replaces the hex values (that
> may have either 3 or 6 characters - plus the #) with the value returned
> from convertHex2Rgba.
>
My personal experience is that you will have written code to do that
long before you discover the magic regexp.

Regexp INCREASES programming time!


> This (one of my many failed attempts):
>
> str.replace(/#[0-9a-fA-F]/, convertHex2Rgba('$1'));
>
> does nothing.
>
> Andrew Poulos


--
The New Left are the people they warned you about.

Andrew Poulos

unread,
Aug 6, 2021, 7:18:01 PMAug 6
to
Doesn't the {3}{1,2} mean, match one or two groups of 3 characters? So I
don't need to check for more than 6. If there's more than 6 then it's
either hexa or invalid.

Andrew Poulos

Andrew Poulos

Andrew Poulos

unread,
Aug 6, 2021, 7:20:07 PMAug 6
to
On 6/08/2021 6:39 pm, Julio Di Egidio wrote:
> On Friday, 6 August 2021 at 09:56:02 UTC+2, Andrew Poulos wrote:
>> On 6/08/2021 5:41 pm, Andrew Poulos wrote:
>>> On 6/08/2021 5:22 pm, Andrew Poulos wrote:
>>>> I have a string that looks like this
>>>>
>>>> let str = 'linear-gradient(90deg, #ff0000 50%, #00ff00 100%)"
>>>>
>>>> I need to convert the hex values to rgba so I've written a function
>>>>
>>>> const convertHex2Rgba = (hex, alpha = 1) => {
>>>> ...
>>>> return `rgba(${r},${g},${b},${alpha})`;
>>>> };
>>>>
>>>> I'm stuck trying to write a regexp that replaces the hex values (that
>>>> may have either 3 or 6 characters - plus the #) with the value
>>>> returned from convertHex2Rgba.
>>>>
>>>> This (one of my many failed attempts):
>>>>
>>>> str.replace(/#[0-9a-fA-F]/, convertHex2Rgba('$1'));
>>>>
>>>> does nothing.
>>>
>>> This has got me a bit closer
>>>
>>> str.replace(/#([A-Fa-f0-9]{3}){1,2}/, convertHex2Rgba('$1'));
>>>
>>> but the match doesn't get replaced.
>> str.replace(/#([A-Fa-f0-9]{3}){1,2}/, match => {
>> return convertHex2Rgba(match);
>> });
>
> Your capturing group in "([A-Fa-f0-9]{3})" which only matches "#" followed by three hex digits: the "{1,2}" won't change that, it means repeated occurrences of that same pattern.

Thanks. though my testing seems to sow that it picks up 6 characters.

Rgeexp (like SQL) seems to me to be like a language one has to be born
speaking ;-)

>> Nearly there, but I have to do it one match at a time.
>
> Because you are missing the 'g' flag, for "global" search and substitution. I have come up with this one, which is as simple as i could make it (while staying correct):
>
> /(#[A-Fa-f0-9]{6}|#[A-Fa-f0-9]{3})/gs
>
> Have a look at the docs and the examples:
> <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#using_an_inline_function_that_modifies_the_matched_characters>
>
> Also, you can prototype with online tools, e.g. as I have done here: <https://regex101.com/r/BdPOyA/1> (you cannot use a replacer function there, but at least you know your pattern is correct).

Nice link.

Andrew Poulos

Julio Di Egidio

unread,
Aug 6, 2021, 8:26:09 PMAug 6
to
On Saturday, 7 August 2021 at 01:20:07 UTC+2, Andrew Poulos wrote:
> On 6/08/2021 6:39 pm, Julio Di Egidio wrote:

> Rgeexp (like SQL) seems to me to be like a language one has to be born
> speaking ;-)

Don't just give a man a fish... Sure, these constructs can get quite intricated to keep track of, which is why "prototyping" is highly advised for pattern writing and checking, anyway, should you find yourself writing regex all day long, be reassured that you'd soon find yourself (mostly) writing it by heart: or, at least as effectively as you would any other language.

Have fun,

Julio

Jon Ribbens

unread,
Aug 7, 2021, 2:57:54 PMAug 7
to
It depends what you want to do if the string contains something like
'#123456789'. You might say that you don't think that will ever happen,
but personally I prefer to err on the side of caution. Also, bear in
mind if you're just chucking random bits of CSS at this then other CSS
tokens such as strings, selectors, comments, and URLs can contain things
matching your regexp (whether or not you use my more-cautious version).

Thomas 'PointedEars' Lahn

unread,
Aug 7, 2021, 3:56:36 PMAug 7
to
Of course not.

1. You are not using the return value of the “replace” method.

2. You are not passing your function a proper value for the formal
parameter “hex”, but the string value '$1' instead.
('$1' is only expanded with the first captured subpattern if
the entire argument is a string value.)

3. You are not matching that which you intend to match.
(You are only matching a single hexadecimal digit.)

This should work:

const generateHex2Rgba =
((alpha = 1) => ((hex, red, green, blue, count) => {
let [r, g, b] = [red, green, blue].map(s => parseInt(s, 16));
return `rgba(${r},${g},${b},${alpha})`;
}));

str = str.replace(
/#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})/gi,
generateHex2Rgba()
);

Why you want to do that remains a mystery, though.

--
PointedEars
FAQ: <http://PointedEars.de/faq> | <http://PointedEars.de/es-matrix>
<https://github.com/PointedEars> | <http://PointedEars.de/wsvn/>
Twitter: @PointedEars2 | Please do not cc me./Bitte keine Kopien per E-Mail.

Thomas 'PointedEars' Lahn

unread,
Aug 7, 2021, 4:02:20 PMAug 7
to
Thomas 'PointedEars' Lahn wrote:

> const generateHex2Rgba =
> ((alpha = 1) => ((hex, red, green, blue, count) => {

I actually recommend that it be written

alpha=1

instead to tell apart the optional argument, and default value of the formal
parameter, from a standard assignment statement.

[Of course, optimizations are possible. For example, having the pattern
outside the callback-generating function is a violation of DRY.]

Andrew Poulos

unread,
Aug 8, 2021, 11:13:55 PMAug 8
to
Nice and concise code but what is 'count' for?

> str = str.replace(
> /#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})/gi,
> generateHex2Rgba()
> );
>
> Why you want to do that remains a mystery, though.

The hex values are from gradient backgrounds that users are grabbing
from online CSS generators (most, if not all, of them return colours in
hex). The backgrounds need to animate so I'm using anime.js to "play"
with the various colour and alpha values. This is easier for me with
rgba than (hex or hexa).

Andrew Poulos


Thomas 'PointedEars' Lahn

unread,
Aug 10, 2021, 7:45:13 AMAug 10
to
Andrew Poulos wrote:

> On 8/08/2021 5:56 am, Thomas 'PointedEars' Lahn wrote:
>> const generateHex2Rgba =
>> ((alpha = 1) => ((hex, red, green, blue, count) => {
>> let [r, g, b] = [red, green, blue].map(s => parseInt(s, 16));
>> return `rgba(${r},${g},${b},${alpha})`;
>> }));
>
> Nice and concise code

Thank you.

> but what is 'count' for?

It should have better been named “offset”, and apparently is only an
artifact of code from my memory of more general code that I had written
before.

The formal parameter that follows the formal parameters for the captures
(here: “red”, “green”, and “blue”) would be assigned the offset of the
match, and the formal parameter that followed that would be assigned the
original string value:

<https://262.ecma-international.org/#sec-string.prototype.replace>

[Do you also yearn for the days when the ECMAScript Language Specification
was written in formal language, yes, and it wasn’t clickable, but at
least it was not completely shrouded in abstractions?

I for one cannot see anymore how that describes the (actual) behavior of
the “replace” method in this case. Where is the check for RegExp?
Or are we supposed to use “replaceAll” now?
(Should I just have brunch first instead?)]

I must have misremembered that it this formal parameter would need to be
specified in order for the capture parameters be assigned the correct value,
as I cannot produce by testing, or find in documentation, supporting
evidence now.

Specifically the following historical example indicates that there is no
backwards compatibility issue, and that it can be safely omitted in this
case:

<http://web.archive.org/web/20040929130418/http://devedge.netscape.com/library/manuals/2000/javascript/1.3/reference/string.html#1194258>

Jon Ribbens

unread,
Aug 10, 2021, 8:29:06 AMAug 10
to
On 2021-08-10, Thomas 'PointedEars' Lahn <Point...@web.de> wrote:
><https://262.ecma-international.org/#sec-string.prototype.replace>
>
> [Do you also yearn for the days when the ECMAScript Language Specification
> was written in formal language, yes, and it wasn’t clickable, but at
> least it was not completely shrouded in abstractions?
>
> I for one cannot see anymore how that describes the (actual) behavior of
> the “replace” method in this case. Where is the check for RegExp?
> Or are we supposed to use “replaceAll” now?
> (Should I just have brunch first instead?)]

Step 2:

If searchValue is neither undefined nor null, then
a. Let replacer be ? GetMethod(searchValue, @@replace).
b. If replacer is not undefined, then
i. Return ? Call(replacer, searchValue, « O, replaceValue »).

RegExp ojects have an @@replace method so that takes over the
handling of the call. Interesting to know that you can presumably
therefore create your own classes with @@replace methods to also
affect the String replace method.

The abstractions in the ECMAScript standard aren't *too* bad I think.
The effort required to find out what's happening in any particular
situation doesn't normally seem to end up being overly burdensome.

Thomas 'PointedEars' Lahn

unread,
Aug 11, 2021, 10:03:23 AMAug 11
to
Jon Ribbens wrote:

> On 2021-08-10, Thomas 'PointedEars' Lahn <Point...@web.de> wrote:
>><https://262.ecma-international.org/#sec-string.prototype.replace>
>> [… I for one cannot see anymore how that describes the (actual) behavior
>> of the “replace” method in this case. Where is the check for RegExp?
>> Or are we supposed to use “replaceAll” now?
>
> Step 2:
>
> If searchValue is neither undefined nor null, then
> a. Let replacer be ? GetMethod(searchValue, @@replace).
> b. If replacer is not undefined, then
> i. Return ? Call(replacer, searchValue, « O, replaceValue »).
>
> RegExp ojects have an @@replace method so that takes over the
> handling of the call. Interesting to know that you can presumably
> therefore create your own classes with @@replace methods to also
> affect the String replace method.

There it is:

<https://262.ecma-international.org/#sec-regexp.prototype-@@replace>

Thank you very much ':-)

> The abstractions in the ECMAScript standard aren't *too* bad I think.
> The effort required to find out what's happening in any particular
> situation doesn't normally seem to end up being overly burdensome.

I have been interpreting them since Edition 3 Final was published at
the latest, and in my opinion it gets worse with every new Edition :'-)


PointedEars
Reply all
Reply to author
Forward
0 new messages