Passed
Push — master ( 2147dc...8d3817 )
by Sebastian
03:06 queued 11s
created

Mailcode_Parser::processMatch()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 28
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 3
eloc 17
c 3
b 0
f 0
nc 3
nop 2
dl 0
loc 28
rs 9.7
1
<?php
2
/**
3
 * File containing the {@see Mailcode_Parser} class.
4
 * 
5
 * @package Mailcode
6
 * @subpackage Parser
7
 * @see Mailcode_Parser
8
 */
9
10
declare(strict_types=1);
11
12
namespace Mailcode;
13
14
use AppUtils\ConvertHelper;
15
16
/**
17
 * Mailcode parser, capable of detecting commands in strings.
18
 * 
19
 * @package Mailcode
20
 * @subpackage Parser
21
 * @author Sebastian Mordziol <[email protected]>
22
 */
23
class Mailcode_Parser
24
{
25
    const COMMAND_REGEX_PARTS = array( 
26
        '{\s*([a-z]+)\s*}',
27
        '{\s*([a-z]+)\s*:([^}]*)}',
28
        '{\s*([a-z]+)\s+([a-z-]+)\s*:([^}]*)}'
29
    );
30
    
31
   /**
32
    * @var Mailcode
33
    */
34
    protected $mailcode;
35
    
36
   /**
37
    * @var Mailcode_Commands
38
    */
39
    protected $commands;
40
41
    /**
42
     * @var Mailcode_Commands_Command_Type_Opening[]
43
     */
44
    protected $stack = array();
45
46
    public function __construct(Mailcode $mailcode)
47
    {
48
        $this->mailcode = $mailcode;
49
        $this->commands = $this->mailcode->getCommands();
50
    }
51
    
52
   /**
53
    * Gets the regex format string used to detect commands.
54
    * 
55
    * @return string
56
    */
57
    protected static function getRegex() : string
58
    {
59
        return '/'.implode('|', self::COMMAND_REGEX_PARTS).'/sixU';
60
    }
61
    
62
   /**
63
    * Parses a string to detect all commands within. Returns a
64
    * collection instance that contains information on all the 
65
    * commands.
66
    * 
67
    * @param string $string
68
    * @return Mailcode_Collection A collection with all unique commands found.
69
    */
70
    public function parseString(string $string) : Mailcode_Collection
71
    {
72
        $collection = new Mailcode_Collection();
73
        
74
        $string = $this->prepareString($string);
75
        
76
        $matches = array();
77
        preg_match_all(self::getRegex(), $string, $matches, PREG_PATTERN_ORDER);
78
        
79
        $total = count($matches[0]);
80
        
81
        for($i=0; $i < $total; $i++)
82
        {
83
            $match = $this->parseMatch($matches, $i);
84
            
85
            $this->processMatch($match, $collection);
86
        }
87
        
88
        return $collection;
89
    }
90
    
91
    protected function prepareString(string $subject) : string
92
    {
93
        if(!ConvertHelper::isStringHTML($subject))
94
        {
95
            return $subject;
96
        }
97
98
        // remove all <style> tags to avoid conflicts with CSS code
99
        return preg_replace('%<style\b[^>]*>(.*?)</style>%six', '', $subject);
100
    }
101
    
102
   /**
103
    * Processes a single match found in the string: creates the command,
104
    * and adds it to the collection if it's a valid command, or to the list
105
    * of invalid commands otherwise.
106
    * 
107
    * @param Mailcode_Parser_Match $match
108
    * @param Mailcode_Collection $collection
109
    */
110
    protected function processMatch(Mailcode_Parser_Match $match, Mailcode_Collection $collection) : void
111
    {
112
        $name = $match->getName();
113
114
        if (!$this->commands->nameExists($name)) {
115
            $collection->addErrorMessage(
116
                $match->getMatchedString(),
117
                t('No command found with the name %1$s.', $name),
118
                Mailcode_Commands_Command::VALIDATION_UNKNOWN_COMMAND_NAME
119
            );
120
            return;
121
        }
122
123
        $cmd = $this->commands->createCommand(
124
            $this->commands->getIDByName($name),
125
            $match->getType(),
126
            $match->getParams(),
127
            $match->getMatchedString()
128
        );
129
130
        if (!$cmd->isValid()) {
131
            $collection->addInvalidCommand($cmd);
132
            return;
133
        }
134
135
        $collection->addCommand($cmd);
136
137
        $this->handleNesting($cmd);
138
    }
139
140
    private function handleNesting(Mailcode_Commands_Command $cmd) : void
141
    {
142
        // Set the command's parent from the stack, if any is present.
143
        if(!empty($this->stack))
144
        {
145
            $cmd->setParent($this->stack[array_key_last($this->stack)]);
146
        }
147
148
        // Handle opening and closing commands, adding and removing from the stack.
149
        if($cmd instanceof Mailcode_Commands_Command_Type_Opening)
150
        {
151
            $this->stack[] = $cmd;
152
        }
153
        else if($cmd instanceof Mailcode_Commands_Command_Type_Closing)
154
        {
155
            array_pop($this->stack);
156
        }
157
    }
158
    
159
   /**
160
    * Parses a single regex match: determines which named group
161
    * matches, and retrieves the according information.
162
    * 
163
    * @param array[] $matches The regex results array.
164
    * @param int $index The matched index.
165
    * @return Mailcode_Parser_Match
166
    */
167
    protected function parseMatch(array $matches, int $index) : Mailcode_Parser_Match
168
    {
169
        $name = ''; // the command name, e.g. "showvar"
170
        $type = '';
171
        $params = '';
172
        
173
        // 1 = single command
174
        // 2 = parameter command, name
175
        // 3 = parameter command, params
176
        // 4 = parameter type command, name
177
        // 5 = parameter type command, type
178
        // 6 = parameter type command, params
179
        
180
        if(!empty($matches[1][$index]))
181
        {
182
            $name = $matches[1][$index];
183
        }
184
        else if(!empty($matches[2][$index]))
185
        {
186
            $name = $matches[2][$index];
187
            $params = $matches[3][$index];
188
        }
189
        else if(!empty($matches[4][$index]))
190
        {
191
            $name = $matches[4][$index];
192
            $type = $matches[5][$index];
193
            $params = $matches[6][$index];
194
        }
195
        
196
        return new Mailcode_Parser_Match(
197
            $name, 
198
            $type, 
199
            $params, 
200
            $matches[0][$index]
201
        );
202
    }
203
    
204
   /**
205
    * Creates an instance of the safeguard tool, which
206
    * is used to safeguard commands in a string with placeholders.
207
    * 
208
    * @param string $subject The string to use to safeguard commands in.
209
    * @return Mailcode_Parser_Safeguard
210
    * @see Mailcode_Parser_Safeguard
211
    */
212
    public function createSafeguard(string $subject) : Mailcode_Parser_Safeguard
213
    {
214
        return new Mailcode_Parser_Safeguard($this, $subject);
215
    }
216
    
217
   /**
218
    * Creates a statement parser, which is used to validate arbitrary
219
    * command statements.
220
    * 
221
    * @param string $statement
222
    * @return Mailcode_Parser_Statement
223
    */
224
    public function createStatement(string $statement) : Mailcode_Parser_Statement
225
    {
226
        return new Mailcode_Parser_Statement($statement);
227
    }
228
}
229