2

I'm a little confused with preg_match and preg_replace. I have a very long content string (from a blog), and I want to find, separate and replace all [caption] tags. Possible tags can be:

[caption]test[/caption]
[caption align="center" caption="test" width="123"]<img src="...">[/caption]
[caption caption="test" align="center" width="123"]<img src="...">[/caption]

etc.

Here's the code I have (but I'm finding that's it not working the way I want it to...):

public function parse_captions($content) {
    if(preg_match("/\[caption(.*) align=\"(.*)\" width=\"(.*)\" caption=\"(.*)\"\](.*)\[\/caption\]/", $content, $c)) {
        $caption = $c[4];         
        $code = "<div>Test<p class='caption-text'>" . $caption . "</p></div>";
        // Here, I'd like to ONLY replace what was found above (since there can be
        // multiple instances
        $content = preg_replace("/\[caption(.*) width=\"(.*)\" caption=\"(.*)\"\](.*)\[\/caption\]/", $code, $content);
    }
    return $content;
}

2 Answers 2

1

The goal is to ignore the content position. You can try this:

$subject = <<<'LOD'
[caption]test1[/caption]
[caption align="center" caption="test2" width="123"][/caption]
[caption caption="test3" align="center" width="123"][/caption]
LOD;

$pattern = <<<'LOD'
~
\[caption                          # begining of the tag 
(?>[^]c]++|c(?!aption\b))*         # followed by anything but c and ]
                                   # or c not followed by "aption"

(?|                                # alternation group
    caption="([^"]++)"[^]]*+]      # the content is inside the begining tag  
  |                                # OR
    ]([^[]+)                       # outside 
)                                  # end of alternation group

\[/caption]                        # closing tag
~x
LOD;

$replacement = "<div>Test<p class='caption-text'>$1</p></div>";

echo htmlspecialchars(preg_replace($pattern, $replacement, $subject));

pattern (condensed version):

$pattern = '~\[caption(?>[^]c]++|c(?!aption\b))*(?|caption="([^"]++)"[^]]*+]|]([^[]++))\[/caption]~';

pattern explanation:

After the begining of the tag you could have content before ] or the caption attribute. This content is describe with:

(?>                # atomic group
    [^]c]++        # all characters that are not ] or c, 1 or more times
  |                # OR
    c(?!aption\b)  # c not followed by aption (to avoid the caption attribute)
)*                 # zero or more times

The alternation group (?| allow multiple capture groups with the same number:

(?|
       # case: the target is in the caption attribute #
    caption="      # (you can replace it by caption\s*+=\s*+")
    ([^"]++)       # all that is not a " one or more times (capture group)
    "
    [^]]*+         # all that is not a ] zero or more times

  |           # OR

       # case: the target is outside the opening tag #
    ]              # square bracket close the opening tag
    ([^[]+)        # all that is not a [ 1 or more times (capture group)
)

The two captures have now the same number #1

Note: if you are sure that each caption tags aren't on several lines, you can add the m modifier at the end of the pattern.

Note2: all quantifiers are possessive and i use atomic groups when it's possible for quick fails and better performances.

Sign up to request clarification or add additional context in comments.

Comments

0

Hint (and not an answer, per se)

Your best method of action would be:

  1. Match everything after caption.

    preg_match("#\[caption(.*?)\]#", $q, $match)
    
  2. Use an explode function for extracting values in $match[1], if any.

    explode(' ', trim($match[1]))
    
  3. Check the values in array returned, and use in your code accordingly.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.