2
\$\begingroup\$

Our site sends users transactional html emails. That content is then stored in a DB for retrieval when the user visits the mailbox section of our site.

The emails which get sent contain media queries in the header, which are lost as our system only pulls in the of the content.

This is why I need to convert at fixed widths that are greater than 530px.

This is my take on replacing fixed widths >= 530px inside then content.

message.replace(/((?:m(?:in|ax)-)?width)\s*(\:\s?|=)\s*(['"\0])?\s*([1-9]\d{3,}|5[3-9][0-9]|[6-9][0-9]{2})(\s*(cm|in|mm|pc|pt|px|q)|(?!%|vw|vh|vmin|vmax|em|ex|ch|rem))[ ]*/gi, '$1$2100%')

It matches both HTML width attributes and CSS based properties that are fixed width values.

Is there a better, more concise way of doing this?

https://regex101.com/r/hdMYGn/6/

Example HTML:

<table align="center" class="container float-center" style="border-spacing: 0; border-collapse: collapse; padding: 0; vertical-align: top; background: #ffffff; width: 580px; margin: 0 auto; Margin: 0 auto; float: none; text-align: center;" width="580">
\$\endgroup\$
  • 3
    \$\begingroup\$ Uhm... changing attributes with RegEx is likely to make your job a ton harder. In addition, you run the risk of bringing insanity, but more importantly, RegEx is not the tool I would be using to try and replace things in an HTML email, or HTML elements in general. Ever. (Fun fact: you have to consider emails which import external CSS instead, for formatting, which is of course valid HTML and won't be captured by your 'regex') \$\endgroup\$ – Thomas Ward Mar 23 '17 at 4:31
  • 2
    \$\begingroup\$ Can you provide more context for using regex for something that is likely easily done with plain JavaScript? Is DOM manipulation an option with the format you are working with? \$\endgroup\$ – Phrancis Mar 23 '17 at 4:40
  • \$\begingroup\$ Can you create two CSS files, one to import when the email is read by the client and other to include when the email is read from your site. By using the different CSS files, you can remove the inline styles and apply styles from the classes/selectors. \$\endgroup\$ – Tushar Mar 23 '17 at 5:42
  • \$\begingroup\$ Can't you just store the information that goes into the place-holder in the email and then format the message from there? \$\endgroup\$ – Graipher Mar 23 '17 at 7:48
3
\$\begingroup\$

Disclaimer

As pointed out in a comment by Thomas Ward, RegEx should not be used to parse HTML or CSS. This answer should only be taken as a review of your regular expressions only, and doesn't imply that I recommend using RegEx to parse HTMl/CSS.

As mentioned by Phrancis, you may use DOM manipulation methods to iterate over elements and set styles on them. Or, you can also use CSS to set styles to containers, such that the elements inside it respect the cascaded styles. For example, setting max-width on container and overflow will make the width of the container fixed and show scroll bars when content inside it overflows.

If you add more context regarding how and where this RegEx is being used, there might be better ways than RegEx. Please edit the question to add more context.


Review

(\:\s?|=)\s*: In this group, it is matching either : followed by optional space or = symbol. : has no special meaning in regular expression i.e. it is not meta-character thus there is no need to escape it. Next, \s? is not required here. As there is \s* after the group, it'll match any number of spaces followed by the one of the symbol from the group. So, \s? can be safely removed.

After making these changes the group will become (:|=)\s*.

It is also observed that character class is faster than OR in groups. Check the demo for [:=] and (:|=) for same input data. Notice the number of steps required to finish match. Character class took 12 steps while group with OR condition took 30 step which is more than twice of the character class.

Now, after using character class the group will become [:=]\s*

Similarly, in other character class 0 is escaped as \0 which is not necessary and can be safely removed.

Let's look at the group (\s*(cm|in|mm|pc|pt|px|q)|(?!%|vw|vh|vmin|vmax|em|ex|ch|rem)).

This matches the unit f or the width property. First, \s* to match space is used. Space cannot be used between the value and unit in CSS. That'll make the property invalid and will not be applied.

After that, there are two groups of units separated by |-OR. These two can be combined in one as (cm|in|mm|pc|pt|px|q|%|vw|vh|vmin|vmax|em|ex|ch|rem).

At the end of regex, [ ]* is used to match any number of spaces. This can be simply written as *(Note this is space followed by *). What is there is a tab instead of space? \s* will solve the problem.

Oh wait, that space is not used anywhere, so it can be removed from regex.

Mistake

  1. If you look closely at the input and output string, you'll notice that the regex is removing single and double quotes from the attribute styles.

    width='530' has become width=100%'.

    To solve this, simply add $3 in the replacement part. The replacement will be now $1$2$3100%.

  2. Another one is that the regex leaves width: 600rem; which is not intended.

    This will be solved when two groups of units are combined. But, this will create an issue of not matching unit-less values like width: 600. By adding empty option in group using | will solve the problem(notice the end of group). Here's how the group will look like

    (cm|in|mm|pc|pt|px|q|%|vw|vh|vmin|vmax|em|ex|ch|rem|)
    

Improvements

Does the above changes really improve the regex?

The above changes brings the number of steps to 1630 from 1881 with one increase in match(rem).

RegEx: ((?:m(?:in|ax)-)?width)\s*([:=])\s*(['"0])?\s*([1-9]\d{3,}|5[3-9][0-9]|[6-9][0-9]{2})(cm|in|mm|pc|pt|px|q|%|vw|vh|vmin|vmax|em|ex|ch|rem|)

Replacement: $1$2$3100%

Demo


UPDATE:

The emails which get sent contain media queries in the header, which are lost as our system only pulls in the of the content.

The media queries related to email can be stored in a separate CSS file and it can be included on the page where email content is shown on your site.

You can also remove all the inline styles from email content HTML and put them in separate CSS file. When sending email, include this CSS file in HTML and when showing the content in your site, include other CSS file. This will show the content correctly in respective sites.

\$\endgroup\$
  • 1
    \$\begingroup\$ Should probably be mentioned that the use of regex to parse and alter HTML/CSS elements is probably bad and there's probably better options... \$\endgroup\$ – Thomas Ward Mar 23 '17 at 5:17
  • \$\begingroup\$ Storing the media queries and pulling them in would be too much dev work as we're phasing out our mailbox feature in a few months. This is a stopgap solution. \$\endgroup\$ – rickysullivan Mar 23 '17 at 5:57
  • \$\begingroup\$ whoops, $1$2$3100% is correct. \$\endgroup\$ – rickysullivan Mar 23 '17 at 6:06
  • \$\begingroup\$ (\s*(cm|in|mm|pc|pt|px|q)|(?!%|vw|vh|vmin|vmax|em|ex|ch|rem)) works by finding only these values cm|in|mm|pc|pt|px|q, they are fixed width. Any variable values ?!%|vw|vh|vmin|vmax|em|ex|ch|rem are left alone by using the negative lookahead. \$\endgroup\$ – rickysullivan Mar 24 '17 at 4:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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