1

I have the following regex from regex capturing with repeating pattern

([0-9]{1,2}h)[ ]*([0-9]{1,2}min):[ ]*(.*(?:\n(?![0-9]{1,2}h).*)*)

It takes the following string

1h 30min: Title 
- Description Line 1
1h 30min: Title
- Description Line 1
- Description Line 2
- Description Line 3

And produces this as a result

Match 1:
  "1h 30min: Title 
  - Description Line 1"

      Group 1: "1h"
      Group 2: "30min"
      Group 3: "Title 
               - Description Line 1"

Match 2:
  "1h 30min: Title 
 - Description Line 1
 - Description Line 2
 - Description Line 3"

      Group 1: "1h"
      Group 2: "30min"
      Group 3: "Title 
               - Description Line 1
               - Description Line 2
               - Description Line 3"

I now have the matching 1h 30min not always occur on a new line. So say I hade the following string

1h 30min: Title 
- Description Line 1 1h 30min: Title - Description Line 1
- Description Line 2
- Description Line 3

How can I modify the regex to get the following matched result?

Match 1:
  "1h 30min: Title 
  - Description Line 1"

      Group 1: "1h"
      Group 2: "30min"
      Group 3: "Title 
               - Description Line 1"

Match 2:
  "1h 30min: Title - Description Line 1
 - Description Line 2
 - Description Line 3"

      Group 1: "1h"
      Group 2: "30min"
      Group 3: "Title - Description Line 1
               - Description Line 2
               - Description Line 3"

I though removing the \n would do the trick but it just ends up matching everything after the first 1h 30min

Ryan King
  • 2,798
  • 9
  • 37
  • 62

3 Answers3

3

You can make this work with only minor changes, but the issue is that last part. The general form of a tempered greedy token is this:

(.(?!notAllowed))+

so, using this pattern for your case, plus adding named groups for clarity:

(?<hours>[0-9]{1,2}h)[ ]*(?<minutes>[0-9]{1,2}min):\s*(?<description>(?:.(?!\dh\s\d{1,2}min))+)

PS: if you cannot turn on a "dot matches newline" mode, you may be able to use [\s\S] to simulate.

regex101 demo

Scott Weaver
  • 6,328
  • 2
  • 23
  • 37
  • 1
    If you see https://www.rexegg.com/regex-quantifiers.html#tempered_greed, you will notice that the `.` must be located *after* the negative lookahead. [See why](https://stackoverflow.com/questions/30900794). – Wiktor Stribiżew Jun 03 '19 at 12:29
2

I can't solve it with minor changes. So, I just offer my solution:

([0-9]{1,2}h) *([0-9]{1,2}min):[\s\S]*?(?=[0-9]{1,2}h|$)
1

The desired output is quite difficult to match, yet not impossible.

I would do part of it, maybe the time and title part with regular expressions, if OK, then the rest with scripting.

Here, we can start with an expression similar to:

([0-9]{1,2}h)\s+([0-9]{1,2}min):\s+(Title)([\d\D]*?\d|.+)|[\s\S]*

or:

([0-9]{1,2}h)\s+([0-9]{1,2}min):\s+([A-Za-z\s]+)([\d\D]*?\d|.+)|[\s\S]*

const regex = /([0-9]{1,2}h)\s+([0-9]{1,2}min):\s+(Title)([\d\D]*?\d|.+)|[\s\S]*/gm;
const str = `1h 30min: Title 
- Description Line 1 1h 30min: Title - Description Line 1
- Description Line 2
- Description Line 3`;
let m;

while ((m = regex.exec(str)) !== null) {
    // This is necessary to avoid infinite loops with zero-width matches
    if (m.index === regex.lastIndex) {
        regex.lastIndex++;
    }
    
    // The result can be accessed through the `m`-variable.
    m.forEach((match, groupIndex) => {
        console.log(`Found match, group ${groupIndex}: ${match}`);
    });
}

RegEx Circuit

jex.im visualizes regular expressions:

enter image description here

Emma
  • 1
  • 9
  • 28
  • 53