Mailcode_Parser::getRegex()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
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 Mailcode\Parser\ParseResult;
15
use Mailcode\Parser\PreParser;
16
17
/**
18
 * Mailcode parser, capable of detecting Mailcode commands in strings.
19
 * 
20
 * @package Mailcode
21
 * @subpackage Parser
22
 * @author Sebastian Mordziol <[email protected]>
23
 */
24
class Mailcode_Parser
25
{
26
    public const ERROR_NOT_A_COMMAND = 73301;
27
28
    public const COMMAND_REGEX_PARTS = array( 
29
        '{\s*([a-z]+)\s*}',
30
        '{\s*([a-z]+)\s*:([^}]*)}',
31
        '{\s*([a-z]+)\s+([a-z-]+)\s*:([^}]*)}'
32
    );
33
    
34
    protected Mailcode $mailcode;
35
    protected Mailcode_Commands $commands;
36
37
    /**
38
     * @var Mailcode_Commands_Command_Type_Opening[]
39
     */
40
    protected array $stack = array();
41
42
    public function __construct(Mailcode $mailcode)
43
    {
44
        $this->mailcode = $mailcode;
45
        $this->commands = $this->mailcode->getCommands();
46
    }
47
    
48
   /**
49
    * Gets the regex format string used to detect commands.
50
    * 
51
    * @return string
52
    */
53
    protected static function getRegex() : string
54
    {
55
        return '/'.implode('|', self::COMMAND_REGEX_PARTS).'/sixU';
56
    }
57
58
    /**
59
     * Parses a string to detect all commands within. Returns a
60
     * collection instance that contains information on all the
61
     * commands.
62
     *
63
     * @param string $string
64
     * @return ParseResult A collection with all unique commands found.
65
     * @throws Mailcode_Exception
66
     */
67
    public function parseString(string $string) : ParseResult
68
    {
69
        $collection = new Mailcode_Collection();
70
71
        $preParser = $this->preParse($string, $collection);
72
73
        $result = new ParseResult($collection, $preParser);
74
75
        if(!$collection->isValid())
76
        {
77
            return $result;
78
        }
79
80
        $string = $this->prepareString($preParser->getString());
81
82
        $matches = array();
83
        preg_match_all(self::getRegex(), $string, $matches, PREG_PATTERN_ORDER);
84
        
85
        $total = count($matches[0]);
86
87
        for($i=0; $i < $total; $i++)
88
        {
89
            $match = $this->parseMatch($matches, $i);
90
            
91
            $this->processMatch($match, $collection);
92
        }
93
94
        $collection->finalize();
95
96
        return $result;
97
    }
98
99
    private function preParse(string $subject, Mailcode_Collection $collection) : PreParser
100
    {
101
        return (new PreParser($subject, $collection))->parse();
102
    }
103
104
    protected function prepareString(string $subject) : string
105
    {
106
        return (new Mailcode_Parser_StringPreProcessor($subject))->process();
107
    }
108
109
    /**
110
     * Processes a single match found in the string: creates the command,
111
     * and adds it to the collection if it's a valid command, or to the list
112
     * of invalid commands otherwise.
113
     *
114
     * @param Mailcode_Parser_Match $match
115
     * @param Mailcode_Collection $collection
116
     * @throws Mailcode_Exception
117
     */
118
    protected function processMatch(Mailcode_Parser_Match $match, Mailcode_Collection $collection) : void
119
    {
120
        $name = $match->getName();
121
122
        if (!$this->commands->nameExists($name)) {
123
            $collection->addErrorMessage(
124
                $match->getMatchedString(),
125
                t('No command found with the name %1$s.', $name),
126
                Mailcode_Commands_Command::VALIDATION_UNKNOWN_COMMAND_NAME
127
            );
128
            return;
129
        }
130
131
        $cmd = $this->commands->createCommand(
132
            $this->commands->getIDByName($name),
133
            $match->getType(),
134
            $match->getParams(),
135
            $match->getMatchedString()
136
        );
137
138
        $this->handleNesting($cmd);
139
140
        if (!$cmd->isValid())
141
        {
142
            $collection->addInvalidCommand($cmd);
143
            return;
144
        }
145
146
        $collection->addCommand($cmd);
147
    }
148
149
    /**
150
     * @param Mailcode_Commands_Command $cmd
151
     * @return void
152
     * @throws Mailcode_Exception
153
     */
154
    private function handleNesting(Mailcode_Commands_Command $cmd) : void
155
    {
156
        // Set the command's parent from the stack, if any is present.
157
        if(!empty($this->stack))
158
        {
159
            $cmd->setParent($this->getStackLast());
160
        }
161
162
        // Handle opening and closing commands, adding and removing from the stack.
163
        if($cmd instanceof Mailcode_Commands_Command_Type_Opening)
164
        {
165
            $this->stack[] = $cmd;
166
        }
167
        else if($cmd instanceof Mailcode_Commands_Command_Type_Closing)
168
        {
169
            array_pop($this->stack);
170
        }
171
    }
172
173
    /**
174
     * @return Mailcode_Commands_Command
175
     * @throws Mailcode_Exception
176
     */
177
    private function getStackLast() : Mailcode_Commands_Command
178
    {
179
        $cmd = $this->stack[array_key_last($this->stack)];
180
181
        if($cmd instanceof Mailcode_Commands_Command)
182
        {
183
            return $cmd;
184
        }
185
186
        throw new Mailcode_Exception('Not a command', '', self::ERROR_NOT_A_COMMAND);
187
    }
188
    
189
   /**
190
    * Parses a single regex match: determines which named group
191
    * matches, and retrieves the according information.
192
    * 
193
    * @param array<int,array<int,string>>$matches The regex results array.
0 ignored issues
show
Bug introduced by
The type Mailcode\The was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
194
    * @param int $index The matched index.
195
    * @return Mailcode_Parser_Match
196
    */
197
    protected function parseMatch(array $matches, int $index) : Mailcode_Parser_Match
198
    {
199
        $name = ''; // the command name, e.g. "showvar"
200
        $type = '';
201
        $params = '';
202
        
203
        // 1 = single command
204
        // 2 = parameter command, name
205
        // 3 = parameter command, params
206
        // 4 = parameter type command, name
207
        // 5 = parameter type command, type
208
        // 6 = parameter type command, params
209
        
210
        if(!empty($matches[1][$index]))
211
        {
212
            $name = $matches[1][$index];
213
        }
214
        else if(!empty($matches[2][$index]))
215
        {
216
            $name = $matches[2][$index];
217
            $params = $matches[3][$index];
218
        }
219
        else if(!empty($matches[4][$index]))
220
        {
221
            $name = $matches[4][$index];
222
            $type = $matches[5][$index];
223
            $params = $matches[6][$index];
224
        }
225
        
226
        return new Mailcode_Parser_Match(
227
            $name, 
228
            $type, 
229
            $params, 
230
            $matches[0][$index]
231
        );
232
    }
233
    
234
   /**
235
    * Creates an instance of the safeguard tool, which
236
    * is used to safeguard commands in a string with placeholders.
237
    * 
238
    * @param string $subject The string to use to safeguard commands in.
239
    * @return Mailcode_Parser_Safeguard
240
    * @see Mailcode_Parser_Safeguard
241
    */
242
    public function createSafeguard(string $subject) : Mailcode_Parser_Safeguard
243
    {
244
        return new Mailcode_Parser_Safeguard($this, $subject);
245
    }
246
247
    /**
248
     * Creates a statement parser, which is used to validate arbitrary
249
     * command statements.
250
     *
251
     * @param string $statement
252
     * @param bool $freeform
253
     * @param Mailcode_Commands_Command|null $sourceCommand
254
     * @return Mailcode_Parser_Statement
255
     */
256
    public function createStatement(string $statement, bool $freeform=false, ?Mailcode_Commands_Command $sourceCommand=null) : Mailcode_Parser_Statement
257
    {
258
        return new Mailcode_Parser_Statement($statement, $freeform, $sourceCommand);
259
    }
260
}
261