Test Failed
Push — master ( d1e019...373b55 )
by Sebastian
02:50
created

Mailcode_Parser::processMatch()   B

Complexity

Conditions 6
Paths 8

Size

Total Lines 43
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 6
eloc 22
c 2
b 0
f 0
nc 8
nop 2
dl 0
loc 43
rs 8.9457
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
        {
116
            $collection->addErrorMessage(
117
                $match->getMatchedString(),
118
                t('No command found with the name %1$s.', $name),
119
                Mailcode_Commands_Command::VALIDATION_UNKNOWN_COMMAND_NAME
120
            );
121
            return;
122
        }
123
        
124
        $cmd = $this->commands->createCommand(
125
            $this->commands->getIDByName($name),
126
            $match->getType(),
127
            $match->getParams(),
128
            $match->getMatchedString()
129
        );
130
        
131
        if(!$cmd->isValid())
132
        {
133
            $collection->addInvalidCommand($cmd);
134
            return;
135
        }
136
137
        $collection->addCommand($cmd);
138
139
        // Set the command's parent from the stack, if any is present.
140
        if(!empty($this->stack))
141
        {
142
            $cmd->setParent($this->stack[array_key_last($this->stack)]);
143
        }
144
145
        // Handle opening and closing commands, adding and removing from the stack.
146
        if($cmd instanceof Mailcode_Commands_Command_Type_Opening)
147
        {
148
            $this->stack[] = $cmd;
149
        }
150
        else if($cmd instanceof Mailcode_Commands_Command_Type_Closing)
151
        {
152
            array_pop($this->stack);
153
        }
154
    }
155
    
156
   /**
157
    * Parses a single regex match: determines which named group
158
    * matches, and retrieves the according information.
159
    * 
160
    * @param array[] $matches The regex results array.
161
    * @param int $index The matched index.
162
    * @return Mailcode_Parser_Match
163
    */
164
    protected function parseMatch(array $matches, int $index) : Mailcode_Parser_Match
165
    {
166
        $name = ''; // the command name, e.g. "showvar"
167
        $type = '';
168
        $params = '';
169
        
170
        // 1 = single command
171
        // 2 = parameter command, name
172
        // 3 = parameter command, params
173
        // 4 = parameter type command, name
174
        // 5 = parameter type command, type
175
        // 6 = parameter type command, params
176
        
177
        if(!empty($matches[1][$index]))
178
        {
179
            $name = $matches[1][$index];
180
        }
181
        else if(!empty($matches[2][$index]))
182
        {
183
            $name = $matches[2][$index];
184
            $params = $matches[3][$index];
185
        }
186
        else if(!empty($matches[4][$index]))
187
        {
188
            $name = $matches[4][$index];
189
            $type = $matches[5][$index];
190
            $params = $matches[6][$index];
191
        }
192
        
193
        return new Mailcode_Parser_Match(
194
            $name, 
195
            $type, 
196
            $params, 
197
            $matches[0][$index]
198
        );
199
    }
200
    
201
   /**
202
    * Creates an instance of the safeguard tool, which
203
    * is used to safeguard commands in a string with placeholders.
204
    * 
205
    * @param string $subject The string to use to safeguard commands in.
206
    * @return Mailcode_Parser_Safeguard
207
    * @see Mailcode_Parser_Safeguard
208
    */
209
    public function createSafeguard(string $subject) : Mailcode_Parser_Safeguard
210
    {
211
        return new Mailcode_Parser_Safeguard($this, $subject);
212
    }
213
    
214
   /**
215
    * Creates a statement parser, which is used to validate arbitrary
216
    * command statements.
217
    * 
218
    * @param string $statement
219
    * @return Mailcode_Parser_Statement
220
    */
221
    public function createStatement(string $statement) : Mailcode_Parser_Statement
222
    {
223
        return new Mailcode_Parser_Statement($statement);
224
    }
225
}
226