Removing everything except numbers inside braces and some characters

Viewed 79

Want to remove everything except # NewLine, complete bracket set and numbers inside braces.
Sample input:

# (1296) {20} [529] [1496] [411]
# (MONDAY ) (1296)
# (646) {20} (BEACH 7) [20 Mtrs] { 03 Foot }
# {19} [455] [721] (1296) (SUNDAY ) [2741] (MONDAY (WEDNESDAY {20}
# {19} (1296)

Code which does not work:

$re = '/(?:\[[^][]*]|\([^()]*\)|{[^{}]*})(*SKIP)(*F)|[^][(){}@#]+/m';
$result = preg_replace($re, '', $input);

Incorrect output:

#(1296){20}[529][1496][411]
#(1296) 
#(646){20}(BEACH 7)[20 Mtrs]{ 03 Foot }
#{19}[455][721](1296)[2741](({20}
#{19}(1296)

Desired output:

#(1296) {20} [529] [1496] [411]
#(1296)
#(646) {20}
#{19} [455] [721] (1296) [2741] {20}
#{19} (1296)
3 Answers

You could match at least 1 digit between the brackets and then skip that match.

Then match any char except a newline or # to be replaced with an empty string.

(?:\[\h*\d[\h\d]*]|\(\h*\d[\h\d]*\)|{\h*\d[\h\d]*})\h*(*SKIP)(*F)|[^#\n]

Explanation

  • (?: Non capture group
    • \[\h*\d[\h\d]*] Match at least 1 digit between square brackets, where \h matches horizontal whitespace characters (no newlines)
    • | Or
    • \(\h*\d[\h\d]*\) 1 digit between parenthesis
    • | Or
    • {\h*\d[\h\d]*} 1 digit between curly braces
  • )\h* Close the non capture group and match 1+ spaces
  • (*SKIP)(*F) Skip and fail the match (to leave it untouched in the output)
  • | Or
  • [^#\n] Match any character except # or a newline

Regex demo

You may match using this regex:

(?>(\()|({)|\[)\h*\d[\h\d]*+(?(1)\)|(?(2)}|]))\h*(*SKIP)(*F)|[][(){}]|[^][(){}\r\n#]+

and replace with an empty string.

RegEx Demo

RegEx Details:

(?>             # Start atomic group
   (\()         # Match ( and capture in group #1
   |            # OR
   ({)          # Match { and capture in group #2
   |            # OR
   \[           # Match [
)               # End atomic group
\h*\d           # Match a digit after 0 or more whitespaces
[\h\d]*+        # Match 0 or more digits or whitespaces
(?(1)           # Condition if capture group 1 is present
   \)           # Match closing )
   |            # Else
   (?(2)        # Condition if capture group 2 is present
      }         # Match closing }
      |         # Else
      ]         # Match closing ]
   )            # End condition of capture group 2
)               # End condition of capture group 1
\h*             # Match 0 or more whitespaces
(*SKIP)(*F)     # Skip and Fail this match that we want to keep
|               # OR
[][(){}]        # Match any of the brackets
|               # OR
[^][(){}\r\n#]+ # Match 1+ of any char not listed inside the [...]

You can use

<?php

$s = "# (1296) {20} [529] [1496] [411]\n# (MONDAY ) (1296)\n# (646) {20} (BEACH 7) [20 Mtrs] { 03 Foot }\n# {19} [455] [721] (1296) (SUNDAY ) [2741] (MONDAY (WEDNESDAY {20}\n# {19} (1296)";
if (preg_match_all('~^#|(?:(\[)|(\()|{)\d+(?(1)]|(?(2)\)|}))~m', $s, $m)) {
  echo preg_replace_callback('~^(#)\s*|#\s*~', function($m) { return isset($m[1]) ? "#" : "\n#"; }, implode(" ", $m[0]));
}

See this PHP demo. Output:

#(1296) {20} [529] [1496] [411] 
#(1296) 
#(646) {20} 
#{19} [455] [721] (1296) [2741] {20} 
#{19} (1296)

Details:

  • ^#|(?:(\[)|(\()|({))\d+(?(1)]|(?(2)\)|})) extracts # at the start of lines, and sequences of one or more digits inside (), [] and {} brackets
  • implode(" ", $m[0]) - creates a single string with spaces between the found matches
  • preg_replace_callback('~^(#)\s*|#\s*~', function($m) { return isset($m[1]) ? "#" : "\n#"; } - replaces the # and whitespaces at the start of string with just #, and all other # with whitespaces after them with a newline + #.
Related