Link to home
Start Free TrialLog in
Avatar of FolkLore
FolkLoreFlag for South Africa

asked on

Regex help needed to write placeholder replacement function

Hi all,

I am busy writing a placeholder replacement system for my CodeIgniter CMS. The premise of my CMS is to make it completely customizable by non-developers for modules that are already developed, by allowing the non-developer to put a placeholder anywhere in his/her template to replace the placeholder with an existing module on the fly.

Example:

<!doctype html>
<html>
<head>
...
...
</head>
    <body>
        [[mod:user:login:home:echo]]
        ...
    </body>
</html>

Open in new window


The regex that I need will have to:

1. Understand that I try to load a module ([[mod:) (could also be an image, box or variable)
2. The module I want to load is "user" and the function is called "login"
3. The remainder of matches (up to 5 parameters) are for parameters for the function. In this example, I am telling the login function to redirect to the home page, and echo out any errors, if found.

I have no idea how to get this working. I have been struggling for days on this.

Herewith my entire code for this section. I guess it would be best to show the whole code section to make it easier for someone to understand.

    function replace_mod($h)
    {
        $ci =& get_instance();
        $gets = explode('/', uri_string());
        $holder = $h;
        $backup = $h;
        $module_pattern = "/\[\[mod:(.*?):(.*?)(?::(.*?))?(?::(.*?))?(?::(.*?))?\]\]/i";
        preg_match_all($module_pattern, $holder, $module_matches);

        // Bring all modules called inside content into play on the page
        if (is_array($module_matches[0]) && count($module_matches[0]) > 0)
        {
            foreach ($module_matches[0] as $key => $val)
            {
                if (file_exists(BASEPATH . '../application/modules/' . $module_matches[1][$key] . '/controllers/' . $module_matches[1][$key] . '.php'))
                {
                    if (!class_exists($module_matches[1][$key]))
                    {
                        $ci->load->module($module_matches[1][$key]);
                    }
                    if (method_exists($module_matches[1][$key], $module_matches[2][$key]))
                    {
                        $is_active = $ci->core->get_module_active($module_always_matches[1][$key]);
                        if (isset($is_active[0]['active']) && $is_active[0]['active'] == 'Yes')
                        {
                            if (!isset($gets[3]) || (isset($gets[3]) && $gets[3] !== 'edit'))
                            {
                                ob_start();
                                if (isset($module_matches[7][$key]) && trim($module_matches[7][$key]) !== '')
                                {
                                    $ci->$module_matches[1][$key]->$module_matches[2][$key]($module_matches[3][$key], $module_matches[4][$key], $module_matches[5][$key], $module_matches[6][$key], $module_matches[7][$key]);
                                }
                                elseif (isset($module_matches[6][$key]) && trim($module_matches[6][$key]) !== '')
                                {
                                    $ci->$module_matches[1][$key]->$module_matches[2][$key]($module_matches[3][$key], $module_matches[4][$key], $module_matches[5][$key], $module_matches[6][$key]);
                                }
                                elseif (isset($module_matches[5][$key]) && trim($module_matches[5][$key]) !== '')
                                {
                                    $ci->$module_matches[1][$key]->$module_matches[2][$key]($module_matches[3][$key], $module_matches[4][$key], $module_matches[5][$key]);
                                }
                                elseif (isset($module_matches[4][$key]) && trim($module_matches[4][$key]) !== '')
                                {
                                    $ci->$module_matches[1][$key]->$module_matches[2][$key]($module_matches[3][$key], $module_matches[4][$key]);
                                }
                                elseif (isset($module_matches[3][$key]) && trim($module_matches[3][$key]) !== '')
                                {
                                    $ci->$module_matches[1][$key]->$module_matches[2][$key]($module_matches[3][$key]);
                                }
                                else
                                {
                                    $ci->$module_matches[1][$key]->$module_matches[2][$key]();
                                }
                                $dats = ob_get_clean();
                                $holder = preg_replace($module_pattern, $dats, $holder, 1);

                                // The change was unsuccessful for some reason - therefore, replaces the placeholder with nothing.
                                if ($holder === $backup)
                                {
                                    $holder = preg_replace($module_pattern, '', $holder, 1);
                                }
                            }
                        }
                    }
                }
                else
                {
                    $holder = preg_replace($module_pattern, '', $holder, 1);
                }
            }
        }
        return $holder;
    }

Open in new window



So:

1. I set the regex (CORRECT).
2. Do a preg_match_all (CORRECT).
3. I check the number of parameters via $gets variable (CORRECT).
4. Checks if the class is loaded, if not, then load it (CORRECT).
5. Checks if the method in the class exists (CORRECT).
6. ob_start(); (CORRECT, I think).
7. Execute the function based on the number of parameters (CORRECT).
8. ob_end_clean(); (CORRECT, I think).

Now here is where things are becoming hairy:

9. Replacements are not done correctly. Here is an example:

Template contains
[[mod:header:load_header:1:echo]]
[[mod:footer:load_footer:1:echo]]

When replacing the header with load_header, this introduces another placeholder:
[[mod:advertising:load_advertising:1:home:hero:echo]]

So, the preg_replace(...) function replaces the header. Then, for some reason, it replaces the advertising one with the footer module. WTF? I think this has to do with the fact that the footer placeholder is the "original" second item, but now becomes the third item because the header placeholder introduced the advertising placeholder.

I tried setting and not setting the "limit" variable, and neither give me the correct result...

In short, it should replace ALL occurrences of the particular match, but only on exact match.

How do I approach this problem? How do I fix it?

Thanks!

Kobus
Avatar of Ray Paseur
Ray Paseur
Flag of United States of America image

I see you haven't used EE very much recently, FolkLore.  Here are some general guidelines to make your use of this site more enjoyable and productive for you...

At EE, the experts exchange answers and advice for points.  If you look at the questions awaiting answers in this zone, you will see a lot of 500 point questions.  Your question is competing for the experts' attention among those high-point questions.  So as a matter of simple economics you might be able to envision which questions will get the experts' attention first.   Just a thought.

I'm not sure how to help you here because I really do not understand the inputs you have and the outputs you want.  But I can say for sure that computer programming is only about one thing: transforming input data into output data.  If you can give us a collection of inputs and show us the corresponding outputs, we can probably help with a regular expression that bridges the gap.

This article may be helpful in guiding your thinking about how to set up the examples.
https://www.experts-exchange.com/Web_Development/Web_Languages-Standards/PHP/A_7830-A-Quick-Tour-of-Test-Driven-Development.html

Best regards, ~Ray
Avatar of Richard Davis
Ray Paseur makes some extremely valuable points, but in a follow-up, I would like to interject that from what you have presented, it looks very much like you're attempting to write a RESTful parser and I naturally have to ask, without any intention of insulting you, why you are wishing to re-invent an already well established wheel?

~AB
Avatar of FolkLore

ASKER

Hi guys,

Thanks for the comments. Yes, I have indeed not been here for a long time. I have now bought a premium subscription to get this question answered, because I have been searching for a long time, and people at Stack Overflow consider my question "too localized" and therefore I can not ask it there.

I am not sure how the points assigned to a question where if I am a premium member. How many points do I have available? I have upped this one to 500 points, let's see if it takes.

To answer your other comments about not knowing what the inputs are and what outputs are desired, I thought I had it nailed when I posted my question, but let me try again with a simpler example:

I have a file called template.php, which contains among other things, the following:

[[mod:header:load_header:echo]]
[[mod:footer:load_footer:echo]]

Open in new window


This instructs my CMS to look for the module "header" and call function "load_header" and  echo the results out on screen. The same happens to the footer. But the problem comes in the fact that the header's view, called "header.php" also has a placeholder in it.

[[mod:advertising:load_advert:home:hero:echo]]

Open in new window


So, at the start of execution of the script (that grabs the CodeIgniter's output via a hook), the regex that I have does a preg_match_all() and thus finds the header and footer replacement items (2 total items).

Now that the header is being replaced, a new placeholder is introduced, namely the advertising one. This is expected (will never have more than a second tier place holder, meaning, advertising module will not have another placeholder itself).

I understand that I will have to run the "filter" a second time, because I now need to replace the advertising placeholder, but for some reason, the ADVERTISING placeholder is now replaced with the footer content, and when running the filter a second time, the FOOTER placeholder now gets replaced by the footer. This results in me getting the header and TWO footers, but what I needed was one header, one advert and one footer.

In short:

Expected:

1. Pass 1 of filter: replace header and footer placeholders with header and footer content respectively.
2. Pass 2 of filter: replace advertisement placeholder with advertisement content.

But what happens:

1. Pass 1 of filter: replace header and advertisement placeholders with header and footer content respectively.
2. Pass 2 of filter: replace footer placeholder with footer content.

I hope this makes more sense now. Using the code above (in the original question), I hope you can solve this problem for me.

Thanks!

Kobus
ASKER CERTIFIED SOLUTION
Avatar of Richard Davis
Richard Davis
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Hi Adrian,

I could, I am sure! I only need the preg_match_all to find the string in the output, but never considered that a normal old explode could actually work a lot better. it will probably be faster as well, as preg* functions are quite intensive... Hmmm... I will try and provide feedback. Thanks for the answer!

Kobus
You're quite welcome. :)
I'm a firm believer in and avid user of Occam's razor when it comes to programming/problem solving.

I will be more than glad to offer more assistance in light of your additional research.

~AB
Hi Adrian,

Occam's razor. Yes, it is amazing how often we make things more complex than they need to be.

I have redeveloped the piece of code, using
str_replace()

Open in new window

as opposed to
preg_replace()

Open in new window

, and used the profiler to determine whether it has a speed implication.

In my case the following is true:

1. Using str_replace() as opposed to preg_replace() makes -0.001 difference :-)
2. One pass vs two passes makes 0.02s difference (Not bad for running it a second time)

You have put me on the right track, therefore, I will accept your answer as solution :-)

Thanks for your help!

Kobus
Occam's razor. Always better to make things as simple as possible. Thanks AB.
It's been a pleasure assisting you, Folklore. I'm very glad this was helpful for you.

Happy coding. :)

~AB