Arrvi
Arrvi

Reputation: 539

Complex recursive regex pattern

I'm trying to do conditional statements in php template system, but I have some problems with making it working.

My syntax is (condition? value to show if condition is true). Matching would be easily accomplished using this pattern: \((\w+)\?(.+?)\). The problem is I need it to work recursively.

I tried these patterns on the string (it should be (a?working(b? with nested conditions).)):

\((\w+)\?(.+?|(?R))\) but it matches (a?working(b? with nested conditions) (skips .) in the end)

\((\w+)\?(.+|(?R))\) but it matches (a?working(b? with nested conditions).)) (everything until last )).

Help me, I'm stuck.

Upvotes: 1

Views: 198

Answers (2)

Arrvi
Arrvi

Reputation: 539

As @Sniffer said, I've made a parser. It's quite messy, but does the job. It is part of template system class. $this->rendered is a string which is being parsed.

const COND_START = '((';
const COND_END = '))';
const COND_SEP = '?';
const COND_NOT = '!';
private function parseConditionals()
{
    for (
        $i=0, 
        $level=0, 
        $levelstart=array(0=>0),
        $levelseparator=array(0=>0),
        $levelname=array(0=>'__main__'); 
        $i < strlen($this->rendered); 
    ) { 
        $startpos = strpos($this->rendered, self::COND_START, $i);
        $seppos = strpos($this->rendered, self::COND_SEP, $i);
        $endpos = strpos($this->rendered, self::COND_END, $i);

        if ( ($startpos === FALSE) && ($endpos === FALSE) ) {
            $i = strlen($this->rendered);
        } elseif ( ($startpos !== FALSE) && $startpos < $endpos ) {
            if ( $seppos < $endpos ) {
                $level++;
                $levelstart[$level] = $startpos;
                $levelseparator[$level] = $seppos;
                $levelname[$level] = substr(
                    $this->rendered, 
                    $startpos+strlen(self::COND_START), 
                    $seppos-$startpos-strlen(self::COND_START)
                );
                $i = $seppos + strlen(self::COND_SEP);
            } else {
                $i = $startpos + strlen(self::COND_START);
            }
        } else {
            $originallen = strlen($this->rendered);
            if ( $level > 0 ) {
                $not = false;
                if ( strpos($levelname[$level], self::COND_NOT) === 0 ) {
                    $levelname[$level] = substr($levelname[$level], strlen(self::COND_SEP));
                    $not = true;
                }
                if ( 
                    !$this->get($levelname[$level]) == $not
                ) {
                    $this->rendered = substr_replace(
                        $this->rendered, 
                        '', 
                        $endpos,
                        strlen(self::COND_END)
                    );
                    $this->rendered = substr_replace(
                        $this->rendered, 
                        '', 
                        $levelstart[$level],
                        $levelseparator[$level]-$levelstart[$level]+strlen(self::COND_SEP)
                    );
                } else {
                    $this->rendered = substr_replace(
                        $this->rendered, 
                        '',
                        $levelstart[$level], 
                        $endpos-$levelstart[$level]+strlen(self::COND_END)
                    );
                }
                $level--;

            }
            $i = $endpos + strlen(self::COND_END);
            $i += strlen($this->rendered)-$originallen;
        }
    }
}

Upvotes: 0

Ibrahim Najjar
Ibrahim Najjar

Reputation: 19423

Try the following pattern:

\((\w+)\?([^()]|\((?!\w+\?)|(?R))+\)

Regex101 Demo

Edit: OK try changing the pattern to the following one:

\((\w+)\?(.+|\((?!\w+\?)|(?R))+\)
          ^^

If this doesn't work as well, try changing .+ to .+?. If all of this doesn't work for you then you probably (I think this is the better solution) need to use a parser instead of regular expressions.

Regex101 Demo 2

Upvotes: 1

Related Questions