Completed
Pull Request — master (#1)
by Fenz
07:21
created
libs/Embedment/PhpEmbedment.php 1 patch
Indentation   +12 added lines, -12 removed lines patch added patch discarded remove patch
@@ -9,17 +9,17 @@
 block discarded – undo
9 9
 
10 10
 class PhpEmbedment extends AEmbedment
11 11
 {
12
-    /**
13
-     * Parsing line.
14
-     *
15
-     * @param \Htsl\ReadingBuffer\Line $line
16
-     *
17
-     * @return \Htsl\Embedment\Contracts
18
-     */
19
-    public function parseLine(Line $line):parent
20
-    {
21
-        $this->content .= $line->content;
12
+	/**
13
+	 * Parsing line.
14
+	 *
15
+	 * @param \Htsl\ReadingBuffer\Line $line
16
+	 *
17
+	 * @return \Htsl\Embedment\Contracts
18
+	 */
19
+	public function parseLine(Line $line):parent
20
+	{
21
+		$this->content .= $line->content;
22 22
 
23
-        return $this;
24
-    }
23
+		return $this;
24
+	}
25 25
 }
Please login to merge, or discard this patch.
libs/Embedment/CodeEmbedment.php 1 patch
Indentation   +15 added lines, -15 removed lines patch added patch discarded remove patch
@@ -9,23 +9,23 @@
 block discarded – undo
9 9
 
10 10
 class CodeEmbedment extends AEmbedment
11 11
 {
12
-    /**
13
-     * Parsing line.
14
-     *
15
-     * @param \Htsl\ReadingBuffer\Line $line
16
-     *
17
-     * @return \Htsl\Embedment\Contracts
18
-     */
19
-    public function parseLine(Line $line):parent
20
-    {
21
-        $content = '<code>'.htmlentities($line->fullContent).'</code>';
12
+	/**
13
+	 * Parsing line.
14
+	 *
15
+	 * @param \Htsl\ReadingBuffer\Line $line
16
+	 *
17
+	 * @return \Htsl\Embedment\Contracts
18
+	 */
19
+	public function parseLine(Line $line):parent
20
+	{
21
+		$content = '<code>'.htmlentities($line->fullContent).'</code>';
22 22
 
23
-        $indentation = $this->document->indentation;
23
+		$indentation = $this->document->indentation;
24 24
 
25
-        false !== $indentation and $content = str_repeat($indentation, $this->document->indentLevel).$content."\n";
25
+		false !== $indentation and $content = str_repeat($indentation, $this->document->indentLevel).$content."\n";
26 26
 
27
-        $this->content .= $content;
27
+		$this->content .= $content;
28 28
 
29
-        return $this;
30
-    }
29
+		return $this;
30
+	}
31 31
 }
Please login to merge, or discard this patch.
libs/Embedment/Contracts/AEmbedment.php 1 patch
Indentation   +51 added lines, -51 removed lines patch added patch discarded remove patch
@@ -9,55 +9,55 @@
 block discarded – undo
9 9
 
10 10
 abstract class AEmbedment
11 11
 {
12
-    /**
13
-     * Embed content.
14
-     *
15
-     * @var string
16
-     */
17
-    protected $content = '';
18
-
19
-    /**
20
-     * The main document which this embedment embedding into.
21
-     *
22
-     * @var [type]
23
-     */
24
-    protected $document;
25
-
26
-    /**
27
-     * Constructor.
28
-     *
29
-     * @param \Htsl\Parser\Document $document
30
-     */
31
-    final public function __construct(Document $document)
32
-    {
33
-        $this->document = $document;
34
-
35
-        $this->construct();
36
-    }
37
-
38
-    /**
39
-     * Getting content.
40
-     *
41
-     * @return string
42
-     */
43
-    final public function getContent():string
44
-    {
45
-        return $this->content;
46
-    }
47
-
48
-    /**
49
-     * Real constructor to be rewrite.
50
-     */
51
-    protected function construct()
52
-    {
53
-    }
54
-
55
-    /**
56
-     * Parsing line.
57
-     *
58
-     * @param \Htsl\ReadingBuffer\Line $line
59
-     *
60
-     * @return \Htsl\Embedment\Contracts
61
-     */
62
-    abstract public function parseLine(Line $line):self;
12
+	/**
13
+	 * Embed content.
14
+	 *
15
+	 * @var string
16
+	 */
17
+	protected $content = '';
18
+
19
+	/**
20
+	 * The main document which this embedment embedding into.
21
+	 *
22
+	 * @var [type]
23
+	 */
24
+	protected $document;
25
+
26
+	/**
27
+	 * Constructor.
28
+	 *
29
+	 * @param \Htsl\Parser\Document $document
30
+	 */
31
+	final public function __construct(Document $document)
32
+	{
33
+		$this->document = $document;
34
+
35
+		$this->construct();
36
+	}
37
+
38
+	/**
39
+	 * Getting content.
40
+	 *
41
+	 * @return string
42
+	 */
43
+	final public function getContent():string
44
+	{
45
+		return $this->content;
46
+	}
47
+
48
+	/**
49
+	 * Real constructor to be rewrite.
50
+	 */
51
+	protected function construct()
52
+	{
53
+	}
54
+
55
+	/**
56
+	 * Parsing line.
57
+	 *
58
+	 * @param \Htsl\ReadingBuffer\Line $line
59
+	 *
60
+	 * @return \Htsl\Embedment\Contracts
61
+	 */
62
+	abstract public function parseLine(Line $line):self;
63 63
 }
Please login to merge, or discard this patch.
libs/Embedment/JsEmbedment.php 1 patch
Indentation   +19 added lines, -19 removed lines patch added patch discarded remove patch
@@ -9,25 +9,25 @@
 block discarded – undo
9 9
 
10 10
 class JsEmbedment extends AEmbedment
11 11
 {
12
-    /**
13
-     * Real constructor.
14
-     */
15
-    protected function construct()
16
-    {
17
-        $this->content .= "\n";
18
-    }
12
+	/**
13
+	 * Real constructor.
14
+	 */
15
+	protected function construct()
16
+	{
17
+		$this->content .= "\n";
18
+	}
19 19
 
20
-    /**
21
-     * Parsing line.
22
-     *
23
-     * @param \Htsl\ReadingBuffer\Line $line
24
-     *
25
-     * @return \Htsl\Embedment\Contracts
26
-     */
27
-    public function parseLine(Line $line):parent
28
-    {
29
-        $this->content .= $line->fullContent."\n";
20
+	/**
21
+	 * Parsing line.
22
+	 *
23
+	 * @param \Htsl\ReadingBuffer\Line $line
24
+	 *
25
+	 * @return \Htsl\Embedment\Contracts
26
+	 */
27
+	public function parseLine(Line $line):parent
28
+	{
29
+		$this->content .= $line->fullContent."\n";
30 30
 
31
-        return $this;
32
-    }
31
+		return $this;
32
+	}
33 33
 }
Please login to merge, or discard this patch.
libs/Embedment/TextEmbedment.php 1 patch
Indentation   +12 added lines, -12 removed lines patch added patch discarded remove patch
@@ -9,17 +9,17 @@
 block discarded – undo
9 9
 
10 10
 class TextEmbedment extends AEmbedment
11 11
 {
12
-    /**
13
-     * Parsing line.
14
-     *
15
-     * @param \Htsl\ReadingBuffer\Line $line
16
-     *
17
-     * @return \Htsl\Embedment\Contracts
18
-     */
19
-    public function parseLine(Line $line):parent
20
-    {
21
-        $this->content .= $line->fullContent."\n";
12
+	/**
13
+	 * Parsing line.
14
+	 *
15
+	 * @param \Htsl\ReadingBuffer\Line $line
16
+	 *
17
+	 * @return \Htsl\Embedment\Contracts
18
+	 */
19
+	public function parseLine(Line $line):parent
20
+	{
21
+		$this->content .= $line->fullContent."\n";
22 22
 
23
-        return $this;
24
-    }
23
+		return $this;
24
+	}
25 25
 }
Please login to merge, or discard this patch.
libs/Embedment/CssEmbedment.php 1 patch
Indentation   +12 added lines, -12 removed lines patch added patch discarded remove patch
@@ -9,17 +9,17 @@
 block discarded – undo
9 9
 
10 10
 class CssEmbedment extends AEmbedment
11 11
 {
12
-    /**
13
-     * Parsing line.
14
-     *
15
-     * @param \Htsl\ReadingBuffer\Line $line
16
-     *
17
-     * @return \Htsl\Embedment\Contracts
18
-     */
19
-    public function parseLine(Line $line):parent
20
-    {
21
-        $this->content .= $line->content;
12
+	/**
13
+	 * Parsing line.
14
+	 *
15
+	 * @param \Htsl\ReadingBuffer\Line $line
16
+	 *
17
+	 * @return \Htsl\Embedment\Contracts
18
+	 */
19
+	public function parseLine(Line $line):parent
20
+	{
21
+		$this->content .= $line->content;
22 22
 
23
-        return $this;
24
-    }
23
+		return $this;
24
+	}
25 25
 }
Please login to merge, or discard this patch.
libs/Parser/HtslParsingException.php 1 patch
Indentation   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -8,5 +8,5 @@
 block discarded – undo
8 8
 
9 9
 class HtslParsingException extends Exception
10 10
 {
11
-    //
11
+	//
12 12
 }
Please login to merge, or discard this patch.
libs/Parser/Document.php 1 patch
Indentation   +867 added lines, -867 removed lines patch added patch discarded remove patch
@@ -19,871 +19,871 @@
 block discarded – undo
19 19
 
20 20
 class Document implements IConfigProvider
21 21
 {
22
-    use TGetter;
23
-
24
-    /**
25
-     * Htsl main object owns this document.
26
-     *
27
-     * @var \Htsl\Htsl
28
-     */
29
-    private $htsl;
30
-
31
-    /**
32
-     * Parent document.
33
-     *
34
-     * @var \Htsl\Parser\Document
35
-     */
36
-    private $parent;
37
-
38
-    /**
39
-     * Reading buffer of this document.
40
-     *
41
-     * @var \Htsl\ReadingBuffer\Contracts\ABuffer
42
-     */
43
-    private $buffer;
44
-
45
-    /**
46
-     * The indentation and whether output with linefeed and indent.
47
-     *
48
-     * @var string | bool
49
-     */
50
-    private $indentation;
51
-
52
-    /**
53
-     * Type of this document.
54
-     *
55
-     * @var string
56
-     */
57
-    private $docType;
58
-
59
-    /**
60
-     * Current embead script.
61
-     *
62
-     * @var \Htsl\Embedment\Contract\Embedment
63
-     */
64
-    private $embedment;
65
-
66
-    /**
67
-     * Current indent level.
68
-     *
69
-     * @var int
70
-     */
71
-    private $level = 0;
72
-
73
-    /**
74
-     * Section indent level.
75
-     *
76
-     * @var int
77
-     */
78
-    private $sectionLevel = 0;
79
-
80
-    /**
81
-     * Opened nodes.
82
-     *
83
-     * @var [ Htsl\Parser\Node\Contracts\ANode, ]
84
-     */
85
-    private $openedNodes = [];
86
-
87
-    /**
88
-     * Current scopes.
89
-     *
90
-     * @var [ Htsl\Parser\Node\Contracts\ANode, ]
91
-     */
92
-    private $scopes = [];
93
-
94
-    /**
95
-     * Current line number.
96
-     *
97
-     * @var int
98
-     */
99
-    private $lineNumber = 0;
100
-
101
-    /**
102
-     * Current line.
103
-     *
104
-     * @var \Htsl\ReadingBuffer\Line
105
-     */
106
-    private $currentLine;
107
-
108
-    /**
109
-     * Sections that can be show.
110
-     *
111
-     * @var [ Htsl\Parser\Section, ]
112
-     */
113
-    private $sections = [];
114
-
115
-    /**
116
-     * Whether the document is executed.
117
-     *
118
-     * @var bool
119
-     */
120
-    private $isExecuted;
121
-
122
-    /**
123
-     * Current Section.
124
-     *
125
-     * @var \Htsl\Parser\Section
126
-     */
127
-    private $currentSection;
128
-
129
-    /**
130
-     * The content of this document.
131
-     *
132
-     * @var string
133
-     */
134
-    private $content;
135
-
136
-    /**
137
-     * Constructor of the Document.
138
-     *
139
-     * @param \Htsl\Htsl                            $htsl
140
-     * @param \Htsl\ReadingBuffer\Contracts\ABuffer $buffer
141
-     * @param \Htsl\Parser\Document | null          $parent
142
-     */
143
-    public function __construct(Htsl $htsl, Buffer $buffer, self $parent = null)
144
-    {
145
-        $this->htsl = $htsl;
146
-        $this->buffer = $buffer;
147
-
148
-        if ($parent) {
149
-            $this->parent = $parent;
150
-            $this->docType = $parent->docType;
151
-            $this->indentation = $parent->indentation;
152
-        } else {
153
-            $this->parseFirstLine();
154
-        }
155
-    }
156
-
157
-    /**
158
-     * Executing the document.
159
-     *
160
-     * @return \Htsl\Parser\Document
161
-     */
162
-    public function execute():self
163
-    {
164
-        if ($this->isExecuted) {
165
-            return $this;
166
-        }
167
-
168
-        return $this->lineByLine()
169
-                    ->bubbleSections();
170
-    }
171
-
172
-    /**
173
-     * Alias of getContent.
174
-     *
175
-     * @return sting
176
-     */
177
-    public function __toString():string
178
-    {
179
-        return $this->getContent();
180
-    }
181
-
182
-    /**
183
-     * Getting the result of document executing.
184
-     *
185
-     * @return string
186
-     */
187
-    protected function getContent():string
188
-    {
189
-        if ($this->parent) {
190
-            return $this->execute()->parent->getContent();
191
-        } else {
192
-            return $this->execute()->content;
193
-        }
194
-    }
195
-
196
-    /**
197
-     * Getting the next line.
198
-     *
199
-     * @return \Htsl\ReadingBuffer\Line
200
-     */
201
-    protected function getLine():Line
202
-    {
203
-        do {
204
-            $line = $this->buffer->getLine();
205
-        } while ($line->isEmpty() && $line->hasMore());
206
-
207
-        return $line;
208
-    }
209
-
210
-    /**
211
-     * Getting the config of type of this document.
212
-     *
213
-     * @param  [ string, ] ...$keys
214
-     *
215
-     * @return mixed
216
-     */
217
-    public function getConfig(string ...$keys)
218
-    {
219
-        return $this->htsl->getConfig(array_shift($keys), $this->docType, ...$keys);
220
-    }
221
-
222
-    /**
223
-     * Getting the type of this document.
224
-     *
225
-     * @return string
226
-     */
227
-    public function getDoctype():string
228
-    {
229
-        return $this->docType;
230
-    }
231
-
232
-    /**
233
-     * Getting the indentation.
234
-     *
235
-     * @return string | bool
236
-     */
237
-    public function getIndentation()
238
-    {
239
-        return $this->indentation;
240
-    }
241
-
242
-    /**
243
-     * Getting the indent level.
244
-     *
245
-     * @return int
246
-     */
247
-    public function getIndentLevel():int
248
-    {
249
-        return $this->level;
250
-    }
251
-
252
-    /**
253
-     * Parsing the first line.
254
-     *
255
-     * @return \Htsl\Parser\Document
256
-     */
257
-    protected function parseFirstLine():self
258
-    {
259
-        $line = $this->getLine();
260
-
261
-        if ('@' === $line->getChar(0)) {
262
-            return $this->setExtending($line);
263
-        }
264
-
265
-        $this->docType = $line->content;
266
-        $docTypeContent = $this->getConfig('doc_types') or $this->throw("DocType $this->docType is not supported");
267
-
268
-        $this->indentation = $this->htsl->getConfig('indentation', $this->docType) ?? (function ($scalarOrFalse) {
269
-            return is_scalar($scalarOrFalse) ? $scalarOrFalse : false;
270
-        })($this->htsl->getConfig('indentation'));
271
-
272
-        $this->appendLine($docTypeContent);
273
-
274
-        return $this;
275
-    }
276
-
277
-    /**
278
-     * Setting that this document extends another document.
279
-     *
280
-     * @param \Htsl\ReadingBuffer\Line $firstLine
281
-     */
282
-    protected function setExtending(Line $firstLine):self
283
-    {
284
-        switch ($name = $firstLine->pregGet('/(?<=^@)[\w-:]+/')) {
285
-            default:{
286
-                $this->throw("The @$name is not supported.");
287
-            }break;
288
-            case 'extend':{
289
-                $this->extend($firstLine->pregGet('/(?<=\( ).*(?= \))/'));
290
-            }break;
291
-            case 'show':
292
-            case 'include':
293
-            case 'section':{
294
-                $this->throw("The @$name can not be used on first line.");
295
-            }break;
296
-        }
297
-
298
-        return $this;
299
-    }
300
-
301
-    /**
302
-     * Parsing this document line by line.
303
-     *
304
-     * @return \Htsl\Parser\Document
305
-     */
306
-    protected function lineByLine():self
307
-    {
308
-        while (($line = $this->getLine())->hasMore()) {
309
-            $this->lineNumber += 1;
310
-
311
-            if ($this->embedment) {
312
-                $this->embeddingParse($line);
313
-            } else {
314
-                $this->parseLine($line);
315
-            }
316
-        }
317
-
318
-        $this->embedment and $this->breakEmbedding();
319
-
320
-        $this->closeNodes($this->level);
321
-
322
-        $this->isExecuted = true;
323
-
324
-        return $this;
325
-    }
326
-
327
-    /**
328
-     * Parsing embedded line.
329
-     *
330
-     * @param \Htsl\ReadingBuffer\Line $line
331
-     *
332
-     * @return \Htsl\Parser\Document
333
-     */
334
-    protected function embeddingParse(Line $line):self
335
-    {
336
-        if ($line->content === '<}') {
337
-            $this->breakEmbedding();
338
-        } else {
339
-            $this->embedment->parseLine($line->getSubIndentLine());
340
-        }
341
-
342
-        return $this;
343
-    }
344
-
345
-    /**
346
-     * Starting the embedding.
347
-     *
348
-     * @param string $embedType
349
-     *
350
-     * @return \Htsl\Parser\Document
351
-     */
352
-    protected function startEmbedding(string $embedType):self
353
-    {
354
-        $embedmentClass = '\\Htsl\\Embedment\\'.ucfirst($embedType).'Embedment';
355
-        class_exists($embedmentClass) or $this->throw("Embed type $embedType not exists.");
356
-
357
-        $this->embedment = new $embedmentClass($this);
358
-
359
-        return $this;
360
-    }
361
-
362
-    /**
363
-     * Ending the embedding.
364
-     *
365
-     * @return \Htsl\Parser\Document
366
-     */
367
-    public function breakEmbedding():self
368
-    {
369
-        $this->append($this->embedment->getContent());
370
-        $this->embedment = null;
371
-
372
-        return $this;
373
-    }
374
-
375
-    /**
376
-     * Parsing line.
377
-     *
378
-     * @param \Htsl\ReadingBuffer\Line $line
379
-     *
380
-     * @return \Htsl\Parser\Document
381
-     */
382
-    protected function parseLine(Line $line):self
383
-    {
384
-        $this->currentLine = $line;
385
-        $this->setLevel($line->getIndentLevel());
386
-
387
-        switch ($line->getChar(0)) {
388
-            default:{
389
-                $this->parseStringLine($line);
390
-            }break;
391
-            case '`':{
392
-                if ('=' === $line->getChar(1)) {
393
-                    $this->parseExpressionHtmlLine($line);
394
-                } else {
395
-                    $this->parseHtmlLine($line);
396
-                }
397
-            }break;
398
-            case '=':{
399
-                $this->parseExpressionLine($line);
400
-            }break;
401
-            case '!':{
402
-                $this->parseCommentLine($line);
403
-            }break;
404
-            case '-':{
405
-                $this->parseTagLine($line);
406
-            }break;
407
-            case '~':{
408
-                $this->parseControlLine($line);
409
-            }break;
410
-            case '@':{
411
-                $this->parseDocControlLine($line);
412
-            }break;
413
-        }
414
-
415
-        return $this;
416
-    }
417
-
418
-    /**
419
-     * Parsing line as HTML content.
420
-     *
421
-     * @param \Htsl\ReadingBuffer\Line $line
422
-     *
423
-     * @return \Htsl\Parser\Document
424
-     */
425
-    protected function parseHtmlLine(Line $line):self
426
-    {
427
-        $node = new StringNode($this, $line);
428
-
429
-        $this->openNode($node);
430
-
431
-        $this->appendLine($line->slice(1));
432
-
433
-        return $this;
434
-    }
435
-
436
-    /**
437
-     * Parsing line as string content.
438
-     *
439
-     * @param \Htsl\ReadingBuffer\Line $line
440
-     *
441
-     * @return \Htsl\Parser\Document
442
-     */
443
-    protected function parseStringLine(Line $line):self
444
-    {
445
-        $node = new StringNode($this, $line);
446
-
447
-        $this->openNode($node);
448
-
449
-        $this->appendLine($this->htmlEntities(trim($line->getContent())));
450
-
451
-        return $this;
452
-    }
453
-
454
-    /**
455
-     * Parsing line as PHP expression.
456
-     *
457
-     * @param \Htsl\ReadingBuffer\Line $line
458
-     *
459
-     * @return \Htsl\Parser\Document
460
-     */
461
-    protected function parseExpressionLine(Line $line):self
462
-    {
463
-        $node = new StringNode($this, $line);
464
-
465
-        $this->openNode($node);
466
-
467
-        $content = $line->slice(1);
468
-        $ent_flag = $this->htsl->getConfig('ENT_flags', $this->docType);
469
-
470
-        $this->appendLine("<?=htmlentities($content,'$ent_flag','UTF-8',false);?>");
471
-
472
-        return $this;
473
-    }
474
-
475
-    /**
476
-     * Parsing line as PHP expression with HTML result.
477
-     *
478
-     * @param \Htsl\ReadingBuffer\Line $line
479
-     *
480
-     * @return \Htsl\Parser\Document
481
-     */
482
-    protected function parseExpressionHtmlLine(Line $line):self
483
-    {
484
-        $node = new StringNode($this, $line);
485
-
486
-        $this->openNode($node);
487
-
488
-        $content = $line->slice(1);
489
-
490
-        $this->appendLine("<?$content?>");
491
-
492
-        return $this;
493
-    }
494
-
495
-    /**
496
-     * Parsing line as comment.
497
-     *
498
-     * @param \Htsl\ReadingBuffer\Line $line
499
-     *
500
-     * @return \Htsl\Parser\Document
501
-     */
502
-    protected function parseCommentLine(Line $line):self
503
-    {
504
-        $node = new CommentNode($this, $line);
505
-
506
-        $this->openNode($node);
507
-
508
-        $this->appendLine($node->open());
509
-
510
-        return $this;
511
-    }
512
-
513
-    /**
514
-     * Parsing line as HTSL tag.
515
-     *
516
-     * @param \Htsl\ReadingBuffer\Line $line
517
-     *
518
-     * @return \Htsl\Parser\Document
519
-     */
520
-    protected function parseTagLine(Line $line):self
521
-    {
522
-        $tag = new TagNode($this, $line);
523
-
524
-        $this->appendLine($tag->open());
525
-
526
-        $tag->embed and $this->startEmbedding($tag->embed);
527
-
528
-        $this->openNode($tag);
529
-
530
-        return $this;
531
-    }
532
-
533
-    /**
534
-     * Parsing line as control node of Htsl.php.
535
-     *
536
-     * @param \Htsl\ReadingBuffer\Line $line
537
-     *
538
-     * @return \Htsl\Parser\Document
539
-     */
540
-    protected function parseControlLine(Line $line):self
541
-    {
542
-        $controlStructure = new ControlNode($this, $line);
543
-
544
-        $this->appendLine($controlStructure->open());
545
-
546
-        $this->openNode($controlStructure);
547
-
548
-        return $this;
549
-    }
550
-
551
-    /**
552
-     * Parsing line as document control node of Htsl.php.
553
-     *
554
-     * @param \Htsl\ReadingBuffer\Line $line
555
-     *
556
-     * @return \Htsl\Parser\Document
557
-     */
558
-    protected function parseDocControlLine(Line $line):self
559
-    {
560
-        switch ($name = $line->pregGet('/(?<=^@)[\w-:]+/')) {
561
-            default:{
562
-                $this->throw("The @$name is not supported.");
563
-            }break;
564
-            case 'extend':{
565
-                $this->throw('The @extend can only be used on first line.');
566
-            }break;
567
-            case 'include':{
568
-                $this->include($line);
569
-            }break;
570
-            case 'section':{
571
-                $this->defineSection($line);
572
-            }break;
573
-            case 'show':{
574
-                $this->showSection($line);
575
-            }break;
576
-        }
577
-
578
-        return $this;
579
-    }
580
-
581
-    /**
582
-     * Parsing extending defination.
583
-     *
584
-     * @param string $fileName
585
-     *
586
-     * @return \Htsl\Parser\Document
587
-     */
588
-    protected function extend(string $fileName):self
589
-    {
590
-        $this->parent = new static($this->htsl, $this->buffer->goSide($fileName), null, $this->indentation);
591
-
592
-        $this->docType = $this->parent->docType;
593
-        $this->indentation = $this->parent->indentation;
594
-
595
-        return $this;
596
-    }
597
-
598
-    /**
599
-     * Include another document.
600
-     *
601
-     * @param \Htsl\ReadingBuffer\Line $line
602
-     *
603
-     * @return \Htsl\Parser\Document
604
-     */
605
-    protected function include(Line $line):self
606
-    {
607
-        $inclued = (new static($this->htsl, $this->buffer->goSide($line->pregGet('/(?<=\( ).*(?= \))/')), $this, $this->indentation))->execute()->content;
608
-
609
-        if (false !== $this->indentation) {
610
-            $inclued = preg_replace('/(?<=^|\\n)(?!$)/', str_repeat($this->indentation, $this->level - $this->sectionLevel), $inclued);
611
-        }
612
-
613
-        $node = new StringNode($this, $line);
614
-
615
-        $this->openNode($node);
616
-
617
-        $this->append($inclued);
618
-
619
-        return $this;
620
-    }
621
-
622
-    /**
623
-     * Starting to define a section.
624
-     *
625
-     * @param \Htsl\ReadingBuffer\Line $line
626
-     *
627
-     * @return \Htsl\Parser\Document
628
-     */
629
-    protected function defineSection(Line $line):self
630
-    {
631
-        $node = new SectionNode($this, $line);
632
-
633
-        $node->open();
634
-
635
-        $this->openNode($node);
636
-
637
-        return $this;
638
-    }
639
-
640
-    /**
641
-     * Showing a section.
642
-     *
643
-     * @param \Htsl\ReadingBuffer\Line $line
644
-     *
645
-     * @return \Htsl\Parser\Document
646
-     */
647
-    protected function showSection(Line $line):self
648
-    {
649
-        $sectionName = $line->pregGet('/(?<=\( ).*(?= \))/');
650
-
651
-        if (!isset($this->sections[$sectionName])) {
652
-            $this->openNode(new StringNode($this, $line));
653
-
654
-            return $this;
655
-        }
656
-        $content = $this->sections[$sectionName]->content;
657
-
658
-        if (false !== $this->indentation) {
659
-            $content = preg_replace('/(?<=^|\\n)(?!$)/', str_repeat($this->indentation, $this->level), $content);
660
-        }
661
-
662
-        $this->append($content);
663
-
664
-        $node = new NamelessSectionNode($this, $line);
665
-
666
-        $node->open();
667
-
668
-        $this->openNode($node);
669
-
670
-        return $this;
671
-    }
672
-
673
-    /**
674
-     * Setting document as section definer.
675
-     *
676
-     * @param Section | null $section
677
-     */
678
-    public function setSection(Section $section = null):self
679
-    {
680
-        if (!$section) {
681
-            $this->sectionLevel = 0;
682
-            $this->currentSection = null;
683
-
684
-            return $this;
685
-        }
686
-
687
-        if ($this->currentSection) {
688
-            $this->throw('Nesting definition of section is forbidden.');
689
-        }
690
-
691
-        if (isset($this->parent->sections[$section->name])) {
692
-            $this->throw("Section $sectionName already defined.");
693
-        }
694
-
695
-        $this->currentSection = $section;
696
-
697
-        if ($section->name) {
698
-            $this->parent->sections[$section->name] = $section;
699
-        }
700
-
701
-        $this->sectionLevel = $this->level + 1;
702
-
703
-        return $this;
704
-    }
705
-
706
-    /**
707
-     * Bubble the sections to parent document.
708
-     *
709
-     * @return \Htsl\Parser\Document
710
-     */
711
-    protected function bubbleSections():self
712
-    {
713
-        if ($this->parent) {
714
-            foreach ($this->sections as $name => $section) {
715
-                if (!isset($this->parent->sections[$name])) {
716
-                    $this->parent->sections[$name] = $section;
717
-                }
718
-            }
719
-        }
720
-
721
-        return $this;
722
-    }
723
-
724
-    /**
725
-     * Escaping characters to HTML entities.
726
-     *
727
-     * @param string $input
728
-     *
729
-     * @return string
730
-     */
731
-    public function htmlEntities(string $input):string
732
-    {
733
-        return htmlentities($input, $this->htsl->getConfig('ENT_flags', $this->docType), 'UTF-8', false);
734
-    }
735
-
736
-    /**
737
-     * Setting indent level of this document.
738
-     *
739
-     * @param int $level
740
-     */
741
-    protected function setLevel(int $level):self
742
-    {
743
-        $level -= $this->level;
744
-
745
-        if ($level <= 0) {
746
-            $this->closeNodes(-$level);
747
-        } elseif ($level == 1) {
748
-            $this->level += 1;
749
-        } else {
750
-            $this->throw('Indent error.');
751
-        }
752
-
753
-        return $this;
754
-    }
755
-
756
-    /**
757
-     * Opening a node.
758
-     *
759
-     * @param \Htsl\Parser\Node\Contracts\ANode $node
760
-     *
761
-     * @return \Htsl\Parser\Document
762
-     */
763
-    protected function openNode(Node $node):self
764
-    {
765
-        array_push($this->openedNodes, $node);
766
-
767
-        $node->scope and $this->setScope($node);
768
-
769
-        return $this;
770
-    }
771
-
772
-    /**
773
-     * Closing open node or nodes.
774
-     *
775
-     * @param int $level
776
-     *
777
-     * @return \Htsl\Parser\Document
778
-     */
779
-    protected function closeNodes(int $level = 0):self
780
-    {
781
-        if (empty($this->openedNodes)) {
782
-            return $this;
783
-        }
784
-
785
-        while ($level-- >= 0) {
786
-            $node = array_pop($this->openedNodes);
787
-
788
-            $node->scope and $this->removeScope($node);
789
-
790
-            $closer = $node->close($this->currentLine) and $this->appendLine($closer);
791
-
792
-            $this->level -= $level >= 0 ? 1 : 0;
793
-        }
794
-
795
-        return $this;
796
-    }
797
-
798
-    /**
799
-     * Pushing a scope to stack.
800
-     *
801
-     * @param \Htsl\Parser\Node\Contracts\ANode $scope
802
-     */
803
-    protected function setScope(Node $scope):int
804
-    {
805
-        return array_unshift($this->scopes, $scope);
806
-    }
807
-
808
-    /**
809
-     * Getting current scope on top of stack.
810
-     *
811
-     * @return \Htsl\Parser\Node\Contracts\ANode | null
812
-     */
813
-    public function getScope()
814
-    {
815
-        return $this->scopes[0] ?? null;
816
-    }
817
-
818
-    /**
819
-     * Pop a scope from stack.
820
-     *
821
-     * @param \Htsl\Parser\Node\Contracts\ANode $scope
822
-     *
823
-     * @return \Htsl\Parser\Document
824
-     */
825
-    protected function removeScope(Node $scope):self
826
-    {
827
-        if ($scope !== array_shift($this->scopes)) {
828
-            $this->throw('Scope nesting error');
829
-        }
830
-
831
-        return $this;
832
-    }
833
-
834
-    /**
835
-     * Appending a line of content to parsing result.
836
-     *
837
-     * @param string $content
838
-     *
839
-     * @return \Htsl\Parser\Document
840
-     */
841
-    protected function appendLine(string $content):self
842
-    {
843
-        if (false !== $this->indentation) {
844
-            $content = str_repeat($this->indentation, $this->level - $this->sectionLevel).$content."\n";
845
-        }
846
-
847
-        return $this->append($content);
848
-    }
849
-
850
-    /**
851
-     * Appending some content to parsing result.
852
-     *
853
-     * @param string $content
854
-     *
855
-     * @return \Htsl\Parser\Document
856
-     */
857
-    protected function append(string $content):self
858
-    {
859
-        if ($this->currentSection) {
860
-            $this->currentSection->append($content);
861
-        } else {
862
-            $this->content .= $content;
863
-        }
864
-
865
-        return $this;
866
-    }
867
-
868
-    /**
869
-     * Getting the Htsl main object.
870
-     *
871
-     * @return \Htsl\Htsl
872
-     */
873
-    public function getHtsl()
874
-    {
875
-        return $this->htsl;
876
-    }
877
-
878
-    /**
879
-     * Throw exception with document name and line number.
880
-     *
881
-     * @param string $message
882
-     *
883
-     * @throw \Htsl\Parser\HtslParsingException
884
-     */
885
-    public function throw(string $message)
886
-    {
887
-        throw new HtslParsingException("$message at file {$this->buffer->fileName} line $this->lineNumber");
888
-    }
22
+	use TGetter;
23
+
24
+	/**
25
+	 * Htsl main object owns this document.
26
+	 *
27
+	 * @var \Htsl\Htsl
28
+	 */
29
+	private $htsl;
30
+
31
+	/**
32
+	 * Parent document.
33
+	 *
34
+	 * @var \Htsl\Parser\Document
35
+	 */
36
+	private $parent;
37
+
38
+	/**
39
+	 * Reading buffer of this document.
40
+	 *
41
+	 * @var \Htsl\ReadingBuffer\Contracts\ABuffer
42
+	 */
43
+	private $buffer;
44
+
45
+	/**
46
+	 * The indentation and whether output with linefeed and indent.
47
+	 *
48
+	 * @var string | bool
49
+	 */
50
+	private $indentation;
51
+
52
+	/**
53
+	 * Type of this document.
54
+	 *
55
+	 * @var string
56
+	 */
57
+	private $docType;
58
+
59
+	/**
60
+	 * Current embead script.
61
+	 *
62
+	 * @var \Htsl\Embedment\Contract\Embedment
63
+	 */
64
+	private $embedment;
65
+
66
+	/**
67
+	 * Current indent level.
68
+	 *
69
+	 * @var int
70
+	 */
71
+	private $level = 0;
72
+
73
+	/**
74
+	 * Section indent level.
75
+	 *
76
+	 * @var int
77
+	 */
78
+	private $sectionLevel = 0;
79
+
80
+	/**
81
+	 * Opened nodes.
82
+	 *
83
+	 * @var [ Htsl\Parser\Node\Contracts\ANode, ]
84
+	 */
85
+	private $openedNodes = [];
86
+
87
+	/**
88
+	 * Current scopes.
89
+	 *
90
+	 * @var [ Htsl\Parser\Node\Contracts\ANode, ]
91
+	 */
92
+	private $scopes = [];
93
+
94
+	/**
95
+	 * Current line number.
96
+	 *
97
+	 * @var int
98
+	 */
99
+	private $lineNumber = 0;
100
+
101
+	/**
102
+	 * Current line.
103
+	 *
104
+	 * @var \Htsl\ReadingBuffer\Line
105
+	 */
106
+	private $currentLine;
107
+
108
+	/**
109
+	 * Sections that can be show.
110
+	 *
111
+	 * @var [ Htsl\Parser\Section, ]
112
+	 */
113
+	private $sections = [];
114
+
115
+	/**
116
+	 * Whether the document is executed.
117
+	 *
118
+	 * @var bool
119
+	 */
120
+	private $isExecuted;
121
+
122
+	/**
123
+	 * Current Section.
124
+	 *
125
+	 * @var \Htsl\Parser\Section
126
+	 */
127
+	private $currentSection;
128
+
129
+	/**
130
+	 * The content of this document.
131
+	 *
132
+	 * @var string
133
+	 */
134
+	private $content;
135
+
136
+	/**
137
+	 * Constructor of the Document.
138
+	 *
139
+	 * @param \Htsl\Htsl                            $htsl
140
+	 * @param \Htsl\ReadingBuffer\Contracts\ABuffer $buffer
141
+	 * @param \Htsl\Parser\Document | null          $parent
142
+	 */
143
+	public function __construct(Htsl $htsl, Buffer $buffer, self $parent = null)
144
+	{
145
+		$this->htsl = $htsl;
146
+		$this->buffer = $buffer;
147
+
148
+		if ($parent) {
149
+			$this->parent = $parent;
150
+			$this->docType = $parent->docType;
151
+			$this->indentation = $parent->indentation;
152
+		} else {
153
+			$this->parseFirstLine();
154
+		}
155
+	}
156
+
157
+	/**
158
+	 * Executing the document.
159
+	 *
160
+	 * @return \Htsl\Parser\Document
161
+	 */
162
+	public function execute():self
163
+	{
164
+		if ($this->isExecuted) {
165
+			return $this;
166
+		}
167
+
168
+		return $this->lineByLine()
169
+					->bubbleSections();
170
+	}
171
+
172
+	/**
173
+	 * Alias of getContent.
174
+	 *
175
+	 * @return sting
176
+	 */
177
+	public function __toString():string
178
+	{
179
+		return $this->getContent();
180
+	}
181
+
182
+	/**
183
+	 * Getting the result of document executing.
184
+	 *
185
+	 * @return string
186
+	 */
187
+	protected function getContent():string
188
+	{
189
+		if ($this->parent) {
190
+			return $this->execute()->parent->getContent();
191
+		} else {
192
+			return $this->execute()->content;
193
+		}
194
+	}
195
+
196
+	/**
197
+	 * Getting the next line.
198
+	 *
199
+	 * @return \Htsl\ReadingBuffer\Line
200
+	 */
201
+	protected function getLine():Line
202
+	{
203
+		do {
204
+			$line = $this->buffer->getLine();
205
+		} while ($line->isEmpty() && $line->hasMore());
206
+
207
+		return $line;
208
+	}
209
+
210
+	/**
211
+	 * Getting the config of type of this document.
212
+	 *
213
+	 * @param  [ string, ] ...$keys
214
+	 *
215
+	 * @return mixed
216
+	 */
217
+	public function getConfig(string ...$keys)
218
+	{
219
+		return $this->htsl->getConfig(array_shift($keys), $this->docType, ...$keys);
220
+	}
221
+
222
+	/**
223
+	 * Getting the type of this document.
224
+	 *
225
+	 * @return string
226
+	 */
227
+	public function getDoctype():string
228
+	{
229
+		return $this->docType;
230
+	}
231
+
232
+	/**
233
+	 * Getting the indentation.
234
+	 *
235
+	 * @return string | bool
236
+	 */
237
+	public function getIndentation()
238
+	{
239
+		return $this->indentation;
240
+	}
241
+
242
+	/**
243
+	 * Getting the indent level.
244
+	 *
245
+	 * @return int
246
+	 */
247
+	public function getIndentLevel():int
248
+	{
249
+		return $this->level;
250
+	}
251
+
252
+	/**
253
+	 * Parsing the first line.
254
+	 *
255
+	 * @return \Htsl\Parser\Document
256
+	 */
257
+	protected function parseFirstLine():self
258
+	{
259
+		$line = $this->getLine();
260
+
261
+		if ('@' === $line->getChar(0)) {
262
+			return $this->setExtending($line);
263
+		}
264
+
265
+		$this->docType = $line->content;
266
+		$docTypeContent = $this->getConfig('doc_types') or $this->throw("DocType $this->docType is not supported");
267
+
268
+		$this->indentation = $this->htsl->getConfig('indentation', $this->docType) ?? (function ($scalarOrFalse) {
269
+			return is_scalar($scalarOrFalse) ? $scalarOrFalse : false;
270
+		})($this->htsl->getConfig('indentation'));
271
+
272
+		$this->appendLine($docTypeContent);
273
+
274
+		return $this;
275
+	}
276
+
277
+	/**
278
+	 * Setting that this document extends another document.
279
+	 *
280
+	 * @param \Htsl\ReadingBuffer\Line $firstLine
281
+	 */
282
+	protected function setExtending(Line $firstLine):self
283
+	{
284
+		switch ($name = $firstLine->pregGet('/(?<=^@)[\w-:]+/')) {
285
+			default:{
286
+				$this->throw("The @$name is not supported.");
287
+			}break;
288
+			case 'extend':{
289
+				$this->extend($firstLine->pregGet('/(?<=\( ).*(?= \))/'));
290
+			}break;
291
+			case 'show':
292
+			case 'include':
293
+			case 'section':{
294
+				$this->throw("The @$name can not be used on first line.");
295
+			}break;
296
+		}
297
+
298
+		return $this;
299
+	}
300
+
301
+	/**
302
+	 * Parsing this document line by line.
303
+	 *
304
+	 * @return \Htsl\Parser\Document
305
+	 */
306
+	protected function lineByLine():self
307
+	{
308
+		while (($line = $this->getLine())->hasMore()) {
309
+			$this->lineNumber += 1;
310
+
311
+			if ($this->embedment) {
312
+				$this->embeddingParse($line);
313
+			} else {
314
+				$this->parseLine($line);
315
+			}
316
+		}
317
+
318
+		$this->embedment and $this->breakEmbedding();
319
+
320
+		$this->closeNodes($this->level);
321
+
322
+		$this->isExecuted = true;
323
+
324
+		return $this;
325
+	}
326
+
327
+	/**
328
+	 * Parsing embedded line.
329
+	 *
330
+	 * @param \Htsl\ReadingBuffer\Line $line
331
+	 *
332
+	 * @return \Htsl\Parser\Document
333
+	 */
334
+	protected function embeddingParse(Line $line):self
335
+	{
336
+		if ($line->content === '<}') {
337
+			$this->breakEmbedding();
338
+		} else {
339
+			$this->embedment->parseLine($line->getSubIndentLine());
340
+		}
341
+
342
+		return $this;
343
+	}
344
+
345
+	/**
346
+	 * Starting the embedding.
347
+	 *
348
+	 * @param string $embedType
349
+	 *
350
+	 * @return \Htsl\Parser\Document
351
+	 */
352
+	protected function startEmbedding(string $embedType):self
353
+	{
354
+		$embedmentClass = '\\Htsl\\Embedment\\'.ucfirst($embedType).'Embedment';
355
+		class_exists($embedmentClass) or $this->throw("Embed type $embedType not exists.");
356
+
357
+		$this->embedment = new $embedmentClass($this);
358
+
359
+		return $this;
360
+	}
361
+
362
+	/**
363
+	 * Ending the embedding.
364
+	 *
365
+	 * @return \Htsl\Parser\Document
366
+	 */
367
+	public function breakEmbedding():self
368
+	{
369
+		$this->append($this->embedment->getContent());
370
+		$this->embedment = null;
371
+
372
+		return $this;
373
+	}
374
+
375
+	/**
376
+	 * Parsing line.
377
+	 *
378
+	 * @param \Htsl\ReadingBuffer\Line $line
379
+	 *
380
+	 * @return \Htsl\Parser\Document
381
+	 */
382
+	protected function parseLine(Line $line):self
383
+	{
384
+		$this->currentLine = $line;
385
+		$this->setLevel($line->getIndentLevel());
386
+
387
+		switch ($line->getChar(0)) {
388
+			default:{
389
+				$this->parseStringLine($line);
390
+			}break;
391
+			case '`':{
392
+				if ('=' === $line->getChar(1)) {
393
+					$this->parseExpressionHtmlLine($line);
394
+				} else {
395
+					$this->parseHtmlLine($line);
396
+				}
397
+			}break;
398
+			case '=':{
399
+				$this->parseExpressionLine($line);
400
+			}break;
401
+			case '!':{
402
+				$this->parseCommentLine($line);
403
+			}break;
404
+			case '-':{
405
+				$this->parseTagLine($line);
406
+			}break;
407
+			case '~':{
408
+				$this->parseControlLine($line);
409
+			}break;
410
+			case '@':{
411
+				$this->parseDocControlLine($line);
412
+			}break;
413
+		}
414
+
415
+		return $this;
416
+	}
417
+
418
+	/**
419
+	 * Parsing line as HTML content.
420
+	 *
421
+	 * @param \Htsl\ReadingBuffer\Line $line
422
+	 *
423
+	 * @return \Htsl\Parser\Document
424
+	 */
425
+	protected function parseHtmlLine(Line $line):self
426
+	{
427
+		$node = new StringNode($this, $line);
428
+
429
+		$this->openNode($node);
430
+
431
+		$this->appendLine($line->slice(1));
432
+
433
+		return $this;
434
+	}
435
+
436
+	/**
437
+	 * Parsing line as string content.
438
+	 *
439
+	 * @param \Htsl\ReadingBuffer\Line $line
440
+	 *
441
+	 * @return \Htsl\Parser\Document
442
+	 */
443
+	protected function parseStringLine(Line $line):self
444
+	{
445
+		$node = new StringNode($this, $line);
446
+
447
+		$this->openNode($node);
448
+
449
+		$this->appendLine($this->htmlEntities(trim($line->getContent())));
450
+
451
+		return $this;
452
+	}
453
+
454
+	/**
455
+	 * Parsing line as PHP expression.
456
+	 *
457
+	 * @param \Htsl\ReadingBuffer\Line $line
458
+	 *
459
+	 * @return \Htsl\Parser\Document
460
+	 */
461
+	protected function parseExpressionLine(Line $line):self
462
+	{
463
+		$node = new StringNode($this, $line);
464
+
465
+		$this->openNode($node);
466
+
467
+		$content = $line->slice(1);
468
+		$ent_flag = $this->htsl->getConfig('ENT_flags', $this->docType);
469
+
470
+		$this->appendLine("<?=htmlentities($content,'$ent_flag','UTF-8',false);?>");
471
+
472
+		return $this;
473
+	}
474
+
475
+	/**
476
+	 * Parsing line as PHP expression with HTML result.
477
+	 *
478
+	 * @param \Htsl\ReadingBuffer\Line $line
479
+	 *
480
+	 * @return \Htsl\Parser\Document
481
+	 */
482
+	protected function parseExpressionHtmlLine(Line $line):self
483
+	{
484
+		$node = new StringNode($this, $line);
485
+
486
+		$this->openNode($node);
487
+
488
+		$content = $line->slice(1);
489
+
490
+		$this->appendLine("<?$content?>");
491
+
492
+		return $this;
493
+	}
494
+
495
+	/**
496
+	 * Parsing line as comment.
497
+	 *
498
+	 * @param \Htsl\ReadingBuffer\Line $line
499
+	 *
500
+	 * @return \Htsl\Parser\Document
501
+	 */
502
+	protected function parseCommentLine(Line $line):self
503
+	{
504
+		$node = new CommentNode($this, $line);
505
+
506
+		$this->openNode($node);
507
+
508
+		$this->appendLine($node->open());
509
+
510
+		return $this;
511
+	}
512
+
513
+	/**
514
+	 * Parsing line as HTSL tag.
515
+	 *
516
+	 * @param \Htsl\ReadingBuffer\Line $line
517
+	 *
518
+	 * @return \Htsl\Parser\Document
519
+	 */
520
+	protected function parseTagLine(Line $line):self
521
+	{
522
+		$tag = new TagNode($this, $line);
523
+
524
+		$this->appendLine($tag->open());
525
+
526
+		$tag->embed and $this->startEmbedding($tag->embed);
527
+
528
+		$this->openNode($tag);
529
+
530
+		return $this;
531
+	}
532
+
533
+	/**
534
+	 * Parsing line as control node of Htsl.php.
535
+	 *
536
+	 * @param \Htsl\ReadingBuffer\Line $line
537
+	 *
538
+	 * @return \Htsl\Parser\Document
539
+	 */
540
+	protected function parseControlLine(Line $line):self
541
+	{
542
+		$controlStructure = new ControlNode($this, $line);
543
+
544
+		$this->appendLine($controlStructure->open());
545
+
546
+		$this->openNode($controlStructure);
547
+
548
+		return $this;
549
+	}
550
+
551
+	/**
552
+	 * Parsing line as document control node of Htsl.php.
553
+	 *
554
+	 * @param \Htsl\ReadingBuffer\Line $line
555
+	 *
556
+	 * @return \Htsl\Parser\Document
557
+	 */
558
+	protected function parseDocControlLine(Line $line):self
559
+	{
560
+		switch ($name = $line->pregGet('/(?<=^@)[\w-:]+/')) {
561
+			default:{
562
+				$this->throw("The @$name is not supported.");
563
+			}break;
564
+			case 'extend':{
565
+				$this->throw('The @extend can only be used on first line.');
566
+			}break;
567
+			case 'include':{
568
+				$this->include($line);
569
+			}break;
570
+			case 'section':{
571
+				$this->defineSection($line);
572
+			}break;
573
+			case 'show':{
574
+				$this->showSection($line);
575
+			}break;
576
+		}
577
+
578
+		return $this;
579
+	}
580
+
581
+	/**
582
+	 * Parsing extending defination.
583
+	 *
584
+	 * @param string $fileName
585
+	 *
586
+	 * @return \Htsl\Parser\Document
587
+	 */
588
+	protected function extend(string $fileName):self
589
+	{
590
+		$this->parent = new static($this->htsl, $this->buffer->goSide($fileName), null, $this->indentation);
591
+
592
+		$this->docType = $this->parent->docType;
593
+		$this->indentation = $this->parent->indentation;
594
+
595
+		return $this;
596
+	}
597
+
598
+	/**
599
+	 * Include another document.
600
+	 *
601
+	 * @param \Htsl\ReadingBuffer\Line $line
602
+	 *
603
+	 * @return \Htsl\Parser\Document
604
+	 */
605
+	protected function include(Line $line):self
606
+	{
607
+		$inclued = (new static($this->htsl, $this->buffer->goSide($line->pregGet('/(?<=\( ).*(?= \))/')), $this, $this->indentation))->execute()->content;
608
+
609
+		if (false !== $this->indentation) {
610
+			$inclued = preg_replace('/(?<=^|\\n)(?!$)/', str_repeat($this->indentation, $this->level - $this->sectionLevel), $inclued);
611
+		}
612
+
613
+		$node = new StringNode($this, $line);
614
+
615
+		$this->openNode($node);
616
+
617
+		$this->append($inclued);
618
+
619
+		return $this;
620
+	}
621
+
622
+	/**
623
+	 * Starting to define a section.
624
+	 *
625
+	 * @param \Htsl\ReadingBuffer\Line $line
626
+	 *
627
+	 * @return \Htsl\Parser\Document
628
+	 */
629
+	protected function defineSection(Line $line):self
630
+	{
631
+		$node = new SectionNode($this, $line);
632
+
633
+		$node->open();
634
+
635
+		$this->openNode($node);
636
+
637
+		return $this;
638
+	}
639
+
640
+	/**
641
+	 * Showing a section.
642
+	 *
643
+	 * @param \Htsl\ReadingBuffer\Line $line
644
+	 *
645
+	 * @return \Htsl\Parser\Document
646
+	 */
647
+	protected function showSection(Line $line):self
648
+	{
649
+		$sectionName = $line->pregGet('/(?<=\( ).*(?= \))/');
650
+
651
+		if (!isset($this->sections[$sectionName])) {
652
+			$this->openNode(new StringNode($this, $line));
653
+
654
+			return $this;
655
+		}
656
+		$content = $this->sections[$sectionName]->content;
657
+
658
+		if (false !== $this->indentation) {
659
+			$content = preg_replace('/(?<=^|\\n)(?!$)/', str_repeat($this->indentation, $this->level), $content);
660
+		}
661
+
662
+		$this->append($content);
663
+
664
+		$node = new NamelessSectionNode($this, $line);
665
+
666
+		$node->open();
667
+
668
+		$this->openNode($node);
669
+
670
+		return $this;
671
+	}
672
+
673
+	/**
674
+	 * Setting document as section definer.
675
+	 *
676
+	 * @param Section | null $section
677
+	 */
678
+	public function setSection(Section $section = null):self
679
+	{
680
+		if (!$section) {
681
+			$this->sectionLevel = 0;
682
+			$this->currentSection = null;
683
+
684
+			return $this;
685
+		}
686
+
687
+		if ($this->currentSection) {
688
+			$this->throw('Nesting definition of section is forbidden.');
689
+		}
690
+
691
+		if (isset($this->parent->sections[$section->name])) {
692
+			$this->throw("Section $sectionName already defined.");
693
+		}
694
+
695
+		$this->currentSection = $section;
696
+
697
+		if ($section->name) {
698
+			$this->parent->sections[$section->name] = $section;
699
+		}
700
+
701
+		$this->sectionLevel = $this->level + 1;
702
+
703
+		return $this;
704
+	}
705
+
706
+	/**
707
+	 * Bubble the sections to parent document.
708
+	 *
709
+	 * @return \Htsl\Parser\Document
710
+	 */
711
+	protected function bubbleSections():self
712
+	{
713
+		if ($this->parent) {
714
+			foreach ($this->sections as $name => $section) {
715
+				if (!isset($this->parent->sections[$name])) {
716
+					$this->parent->sections[$name] = $section;
717
+				}
718
+			}
719
+		}
720
+
721
+		return $this;
722
+	}
723
+
724
+	/**
725
+	 * Escaping characters to HTML entities.
726
+	 *
727
+	 * @param string $input
728
+	 *
729
+	 * @return string
730
+	 */
731
+	public function htmlEntities(string $input):string
732
+	{
733
+		return htmlentities($input, $this->htsl->getConfig('ENT_flags', $this->docType), 'UTF-8', false);
734
+	}
735
+
736
+	/**
737
+	 * Setting indent level of this document.
738
+	 *
739
+	 * @param int $level
740
+	 */
741
+	protected function setLevel(int $level):self
742
+	{
743
+		$level -= $this->level;
744
+
745
+		if ($level <= 0) {
746
+			$this->closeNodes(-$level);
747
+		} elseif ($level == 1) {
748
+			$this->level += 1;
749
+		} else {
750
+			$this->throw('Indent error.');
751
+		}
752
+
753
+		return $this;
754
+	}
755
+
756
+	/**
757
+	 * Opening a node.
758
+	 *
759
+	 * @param \Htsl\Parser\Node\Contracts\ANode $node
760
+	 *
761
+	 * @return \Htsl\Parser\Document
762
+	 */
763
+	protected function openNode(Node $node):self
764
+	{
765
+		array_push($this->openedNodes, $node);
766
+
767
+		$node->scope and $this->setScope($node);
768
+
769
+		return $this;
770
+	}
771
+
772
+	/**
773
+	 * Closing open node or nodes.
774
+	 *
775
+	 * @param int $level
776
+	 *
777
+	 * @return \Htsl\Parser\Document
778
+	 */
779
+	protected function closeNodes(int $level = 0):self
780
+	{
781
+		if (empty($this->openedNodes)) {
782
+			return $this;
783
+		}
784
+
785
+		while ($level-- >= 0) {
786
+			$node = array_pop($this->openedNodes);
787
+
788
+			$node->scope and $this->removeScope($node);
789
+
790
+			$closer = $node->close($this->currentLine) and $this->appendLine($closer);
791
+
792
+			$this->level -= $level >= 0 ? 1 : 0;
793
+		}
794
+
795
+		return $this;
796
+	}
797
+
798
+	/**
799
+	 * Pushing a scope to stack.
800
+	 *
801
+	 * @param \Htsl\Parser\Node\Contracts\ANode $scope
802
+	 */
803
+	protected function setScope(Node $scope):int
804
+	{
805
+		return array_unshift($this->scopes, $scope);
806
+	}
807
+
808
+	/**
809
+	 * Getting current scope on top of stack.
810
+	 *
811
+	 * @return \Htsl\Parser\Node\Contracts\ANode | null
812
+	 */
813
+	public function getScope()
814
+	{
815
+		return $this->scopes[0] ?? null;
816
+	}
817
+
818
+	/**
819
+	 * Pop a scope from stack.
820
+	 *
821
+	 * @param \Htsl\Parser\Node\Contracts\ANode $scope
822
+	 *
823
+	 * @return \Htsl\Parser\Document
824
+	 */
825
+	protected function removeScope(Node $scope):self
826
+	{
827
+		if ($scope !== array_shift($this->scopes)) {
828
+			$this->throw('Scope nesting error');
829
+		}
830
+
831
+		return $this;
832
+	}
833
+
834
+	/**
835
+	 * Appending a line of content to parsing result.
836
+	 *
837
+	 * @param string $content
838
+	 *
839
+	 * @return \Htsl\Parser\Document
840
+	 */
841
+	protected function appendLine(string $content):self
842
+	{
843
+		if (false !== $this->indentation) {
844
+			$content = str_repeat($this->indentation, $this->level - $this->sectionLevel).$content."\n";
845
+		}
846
+
847
+		return $this->append($content);
848
+	}
849
+
850
+	/**
851
+	 * Appending some content to parsing result.
852
+	 *
853
+	 * @param string $content
854
+	 *
855
+	 * @return \Htsl\Parser\Document
856
+	 */
857
+	protected function append(string $content):self
858
+	{
859
+		if ($this->currentSection) {
860
+			$this->currentSection->append($content);
861
+		} else {
862
+			$this->content .= $content;
863
+		}
864
+
865
+		return $this;
866
+	}
867
+
868
+	/**
869
+	 * Getting the Htsl main object.
870
+	 *
871
+	 * @return \Htsl\Htsl
872
+	 */
873
+	public function getHtsl()
874
+	{
875
+		return $this->htsl;
876
+	}
877
+
878
+	/**
879
+	 * Throw exception with document name and line number.
880
+	 *
881
+	 * @param string $message
882
+	 *
883
+	 * @throw \Htsl\Parser\HtslParsingException
884
+	 */
885
+	public function throw(string $message)
886
+	{
887
+		throw new HtslParsingException("$message at file {$this->buffer->fileName} line $this->lineNumber");
888
+	}
889 889
 }
Please login to merge, or discard this patch.
libs/Parser/Node/TagNode.php 1 patch
Indentation   +345 added lines, -345 removed lines patch added patch discarded remove patch
@@ -10,352 +10,352 @@
 block discarded – undo
10 10
 
11 11
 class TagNode extends ANode implements ArrayAccess
12 12
 {
13
-    /**
14
-     * The html tag name of this node.
15
-     *
16
-     * @var string
17
-     */
18
-    private $tagName;
19
-
20
-    /**
21
-     * Whether the html tag is empty.
22
-     *
23
-     * @var bool
24
-     */
25
-    private $isEmpty;
26
-
27
-    /**
28
-     * The attributes of this node.
29
-     *
30
-     * @var array
31
-     */
32
-    private $attributes = [];
33
-
34
-    /**
35
-     * Real constructor.
36
-     *
37
-     * @return \Htsl\Parser\Node\Contracts\ANode
38
-     */
39
-    protected function construct():parent
40
-    {
41
-        $name = $this->line->pregGet('/(?<=^-)[\w-:]+/');
42
-        $this->name = $name;
43
-
44
-        $this->loadConfig($name, $this->document);
45
-
46
-        $this->tagName = $this->config['name'] ?? $name;
47
-        $this->isEmpty = $this->line->getChar(-1) === '/' || $this->document->getConfig('empty_tags', $this->tagName);
48
-        isset($this->config['default_attributes']) and array_walk($this->config['default_attributes'], function ($value, $key) {
49
-            $this->setAttribute($key, $value);
50
-        });
51
-
52
-        return $this;
53
-    }
54
-
55
-    /**
56
-     * Opening this tag node, and returning node opener.
57
-     *
58
-     * @return string
59
-     */
60
-    public function open():string
61
-    {
62
-        if (isset($this->config['opener'])) {
63
-            return $this->config['opener'];
64
-        }
65
-
66
-        if (isset($this->config['params'])) {
67
-            $this->parseParams();
68
-        }
69
-
70
-        if (isset($this->config['name_value'])) {
71
-            $this->parseNameValue();
72
-        }
73
-
74
-        if (isset($this->config['link'])) {
75
-            $this->parseLink();
76
-        }
77
-
78
-        if (isset($this->config['target'])) {
79
-            $this->parseTarget();
80
-        }
81
-
82
-        if (isset($this->config['alt'])) {
83
-            $this->parseAlt();
84
-        }
85
-
86
-        $this->parseCommonAttributes();
87
-
88
-        if (isset($this->config['in_scope']) && isset($this->config['scope_function']) && is_callable($this->config['scope_function'])) {
89
-            $this->config['scope_function']->call($this, $this->document->scope);
90
-        }
91
-
92
-        $finisher = $this->isEmpty ? ' />' : '>';
93
-
94
-        return "<{$this->tagName}{$this->attributesString}{$finisher}";
95
-    }
96
-
97
-    /**
98
-     * Close this tag node, and returning node closer.
99
-     *
100
-     * @param \Htsl\ReadingBuffer\Line $closerLine The line when node closed.
101
-     *
102
-     * @return string
103
-     */
104
-    public function close(Line $Line):string
105
-    {
106
-        return $this->isEmpty ? '' : $this->config['closer'] ?? "</{$this->tagName}>";
107
-    }
108
-
109
-    /**
110
-     * Getting whether this is embedding node and embeding type.
111
-     *
112
-     * @return string
113
-     */
114
-    public function getEmbed():string
115
-    {
116
-        return $this->config['embedding'] ?? '';
117
-    }
118
-
119
-    /**
120
-     * Getting whether this node contains a scope and scope name.
121
-     *
122
-     * @return string | null
123
-     */
124
-    public function getScope()
125
-    {
126
-        return $this->config['scope'] ?? null;
127
-    }
128
-
129
-    /**
130
-     * Parsing node parameters if needed.
131
-     *
132
-     * @return \Htsl\Parser\Node\TagNode
133
-     */
134
-    protected function parseParams():self
135
-    {
136
-        $params = preg_split('/(?<!\\\\)\\|/', $this->line->pregGet('/^-[\w-:]+\((.*?)\)(?= |(\\{>)?$)/', 1));
137
-
138
-        if (($m = count($params)) != ($n = count($this->config['params']))) {
139
-            $this->document->throw("Tag $this->name has $n parameters $m given.");
140
-        }
141
-
142
-        array_map(function ($key, $value) {
143
-            return $this->setAttribute($key, str_replace('\\|', '|', $value));
144
-        }, $this->config['params'], $params);
145
-
146
-        return $this;
147
-    }
148
-
149
-    /**
150
-     * Parsing <name|value> attributes.
151
-     *
152
-     * @return \Htsl\Parser\Node\TagNode
153
-     */
154
-    protected function parseNameValue():self
155
-    {
156
-        $params = $this->line->pregGet('/ <(.*?)>(?= |$)/', 1)
157
-         and $params = preg_split('/(?<!\\\\)\\|/', $params)
158
-          and array_map(function ($key, $value) {
159
-              return isset($key) && isset($value) ? $this->setAttribute($key, $this->checkExpression(str_replace('\\|', '|', $value))) : '';
160
-          }, $this->config['name_value'], $params);
161
-
162
-        return $this;
163
-    }
164
-
165
-    /**
166
-     * Parsing @links.
167
-     *
168
-     * @return \Htsl\Parser\Node\TagNode
169
-     */
170
-    protected function parseLink():self
171
-    {
172
-        $link = $this->line->pregGet('/ @((?!\()(?:[^ ]| (?=[a-zA-Z0-9]))+|(?<exp>\((?:[^()]+|(?&exp)?)+?\)))(?= |$)/', 1);
173
-
174
-        if (strlen($link)) {
175
-            if (isset($this->config['target']) && ':' === $link[0]) {
176
-                $this->setAttribute($this->config['link'], 'javascript'.$link);
177
-            } elseif ('//' === ($firstTwoLetters = substr($link, 0, 2))) {
178
-                $this->setAttribute($this->config['link'], 'http:'.$link);
179
-            } elseif ('\\\\' === $firstTwoLetters) {
180
-                $this->setAttribute($this->config['link'], 'https://'.substr($link, 2));
181
-            } else {
182
-                $this->setAttribute($this->config['link'], $this->checkExpression($link));
183
-            }
184
-        }
185
-
186
-        return $this;
187
-    }
188
-
189
-    /**
190
-     * Parsing >target.
191
-     *
192
-     * @return \Htsl\Parser\Node\TagNode
193
-     */
194
-    protected function parseTarget():self
195
-    {
196
-        $target = $this->line->pregGet('/ >((?!\()(?:[^ ]| (?=[a-zA-Z0-9]))+|(?<exp>\((?:[^()]+|(?&exp)?)+?\)))(?= |$)/', 1);
197
-
198
-        if (strlen($target)) {
199
-            $this->setAttribute($this->config['target'], $this->checkExpression($target));
200
-        }
201
-
202
-        return $this;
203
-    }
204
-
205
-    /**
206
-     * Parsing _placeholders.
207
-     *
208
-     * @return \Htsl\Parser\Node\TagNode
209
-     */
210
-    protected function parseAlt():self
211
-    {
212
-        $alt = $this->line->pregGet('/ _((?!\()(?:[^ ]| (?=[a-zA-Z0-9]))+|(?<exp>\((?:[^()]+|(?&exp)?)+?\)))(?= |$)/', 1);
213
-
214
-        if (strlen($alt)) {
215
-            $this->setAttribute($this->config['alt'], $this->checkExpression($alt));
216
-        }
217
-
218
-        return $this;
219
-    }
220
-
221
-    /**
222
-     * Parsing #ids .classes ^titles [styles] %event{>listeners<} and {other attributes}.
223
-     *
224
-     * @return \Htsl\Parser\Node\TagNode
225
-     */
226
-    protected function parseCommonAttributes():string
227
-    {
228
-        $attributes = '';
229
-
230
-        $id = $this->line->pregGet('/ #([^ ]+|(?<exp>\((?:[^()]+|(?&exp)?)+?\)))(?= |$)/', 1)
231
-         and $this->setAttribute('id', $id);
232
-
233
-        $classes = $this->line->pregGet('/ \.[^ ]+(?= |$)/')
234
-         and preg_match_all('/\.((?(?!\()[^.]+|(?<exp>\((?:[^()]+|(?&exp)?)+?\))))/', $classes, $matches)
235
-          and $classes = implode(' ', array_filter(array_map(function ($className) {
236
-              return $this->checkExpression($className);
237
-          }, $matches[1])))
238
-           and $this->setAttribute('class', $classes);
239
-
240
-        $title = $this->line->pregGet('/ \^((?!\()(?:[^ ]| (?=[a-zA-Z0-9]))+|(?<exp>\((?:[^()]+|(?&exp)?)+?\)))(?= |$)/', 1)
241
-         and $this->setAttribute('title', $title);
242
-
243
-        $style = $this->line->pregGet('/ \[([^\]]+;)(?=\]( |$))/', 1)
244
-         and $this->setAttribute('style', $style);
245
-
246
-        $eventListeners = $this->line->pregMap('/ %(\w+)\{>(.*?)<\}(?= |$)/', function ($string, $name, $code) {
247
-            $this->setAttribute('on'.$name, str_replace('"', '&quot;', $code));
248
-        })
249
-         and implode('', $eventListeners);
250
-
251
-        $other = $this->line->pregGet('/(?<=\{).*?(?=;\}( |$))/')
252
-         and array_map(function ($keyValue) {
253
-             preg_replace_callback('/^([\w-:]+)(?:\?(.+?))?(?:\=(.*))?$/', function ($matches) {
254
-                 $this->setAttribute($matches[1], ($matches[3] ?? $matches[1]) ?: $matches[1], $matches[2] ?? null);
255
-             }, $keyValue);
256
-         }, explode(';', $other));
257
-
258
-        return $attributes;
259
-    }
260
-
261
-    /**
262
-     * Checking and parse PHP expressions.
263
-     *
264
-     * @param string $value
265
-     *
266
-     * @return string
267
-     */
268
-    protected function checkExpression(string $value):string
269
-    {
270
-        return preg_match('/^\(.*\)$/', $value) ? '<?='.substr($value, 1, -1).';?>' : str_replace('"', '&quot;', $value);
271
-    }
272
-
273
-    /**
274
-     * Getting attribute string with HTML syntax.
275
-     *
276
-     * @return string
277
-     */
278
-    protected function getAttributesString():string
279
-    {
280
-        ksort($this->attributes);
281
-
282
-        return implode('', array_map(static function (string $key, array $data) {
283
-            return isset($data['condition']) && strlen($data['condition']) ?
284
-                "<?php if( {$data['condition']} ){?> $key=\"{$data['value']}\"<?php }?>"
285
-                :
286
-                " $key=\"{$data['value']}\"";
287
-        }, array_keys($this->attributes), $this->attributes));
288
-    }
289
-
290
-    /**
291
-     * Setting attribute.
292
-     *
293
-     * @param string      $key       Attribute name.
294
-     * @param string      $value     Attribute value
295
-     * @param string|null $condition Optional condition, If given, attribute will seted only when condition is true.
296
-     */
297
-    protected function setAttribute(string $key, string $value, string $condition = null):self
298
-    {
299
-        if (isset($this->attributes[$key])) {
300
-            $this->document->throw("Attribute $key of $this->name cannot redeclare.");
301
-        }
302
-
303
-        $this->attributes[$key] = [
304
-            'value'     => $value,
305
-            'condition' => $condition,
306
-        ];
307
-
308
-        return $this;
309
-    }
310
-
311
-    /*             *\
13
+	/**
14
+	 * The html tag name of this node.
15
+	 *
16
+	 * @var string
17
+	 */
18
+	private $tagName;
19
+
20
+	/**
21
+	 * Whether the html tag is empty.
22
+	 *
23
+	 * @var bool
24
+	 */
25
+	private $isEmpty;
26
+
27
+	/**
28
+	 * The attributes of this node.
29
+	 *
30
+	 * @var array
31
+	 */
32
+	private $attributes = [];
33
+
34
+	/**
35
+	 * Real constructor.
36
+	 *
37
+	 * @return \Htsl\Parser\Node\Contracts\ANode
38
+	 */
39
+	protected function construct():parent
40
+	{
41
+		$name = $this->line->pregGet('/(?<=^-)[\w-:]+/');
42
+		$this->name = $name;
43
+
44
+		$this->loadConfig($name, $this->document);
45
+
46
+		$this->tagName = $this->config['name'] ?? $name;
47
+		$this->isEmpty = $this->line->getChar(-1) === '/' || $this->document->getConfig('empty_tags', $this->tagName);
48
+		isset($this->config['default_attributes']) and array_walk($this->config['default_attributes'], function ($value, $key) {
49
+			$this->setAttribute($key, $value);
50
+		});
51
+
52
+		return $this;
53
+	}
54
+
55
+	/**
56
+	 * Opening this tag node, and returning node opener.
57
+	 *
58
+	 * @return string
59
+	 */
60
+	public function open():string
61
+	{
62
+		if (isset($this->config['opener'])) {
63
+			return $this->config['opener'];
64
+		}
65
+
66
+		if (isset($this->config['params'])) {
67
+			$this->parseParams();
68
+		}
69
+
70
+		if (isset($this->config['name_value'])) {
71
+			$this->parseNameValue();
72
+		}
73
+
74
+		if (isset($this->config['link'])) {
75
+			$this->parseLink();
76
+		}
77
+
78
+		if (isset($this->config['target'])) {
79
+			$this->parseTarget();
80
+		}
81
+
82
+		if (isset($this->config['alt'])) {
83
+			$this->parseAlt();
84
+		}
85
+
86
+		$this->parseCommonAttributes();
87
+
88
+		if (isset($this->config['in_scope']) && isset($this->config['scope_function']) && is_callable($this->config['scope_function'])) {
89
+			$this->config['scope_function']->call($this, $this->document->scope);
90
+		}
91
+
92
+		$finisher = $this->isEmpty ? ' />' : '>';
93
+
94
+		return "<{$this->tagName}{$this->attributesString}{$finisher}";
95
+	}
96
+
97
+	/**
98
+	 * Close this tag node, and returning node closer.
99
+	 *
100
+	 * @param \Htsl\ReadingBuffer\Line $closerLine The line when node closed.
101
+	 *
102
+	 * @return string
103
+	 */
104
+	public function close(Line $Line):string
105
+	{
106
+		return $this->isEmpty ? '' : $this->config['closer'] ?? "</{$this->tagName}>";
107
+	}
108
+
109
+	/**
110
+	 * Getting whether this is embedding node and embeding type.
111
+	 *
112
+	 * @return string
113
+	 */
114
+	public function getEmbed():string
115
+	{
116
+		return $this->config['embedding'] ?? '';
117
+	}
118
+
119
+	/**
120
+	 * Getting whether this node contains a scope and scope name.
121
+	 *
122
+	 * @return string | null
123
+	 */
124
+	public function getScope()
125
+	{
126
+		return $this->config['scope'] ?? null;
127
+	}
128
+
129
+	/**
130
+	 * Parsing node parameters if needed.
131
+	 *
132
+	 * @return \Htsl\Parser\Node\TagNode
133
+	 */
134
+	protected function parseParams():self
135
+	{
136
+		$params = preg_split('/(?<!\\\\)\\|/', $this->line->pregGet('/^-[\w-:]+\((.*?)\)(?= |(\\{>)?$)/', 1));
137
+
138
+		if (($m = count($params)) != ($n = count($this->config['params']))) {
139
+			$this->document->throw("Tag $this->name has $n parameters $m given.");
140
+		}
141
+
142
+		array_map(function ($key, $value) {
143
+			return $this->setAttribute($key, str_replace('\\|', '|', $value));
144
+		}, $this->config['params'], $params);
145
+
146
+		return $this;
147
+	}
148
+
149
+	/**
150
+	 * Parsing <name|value> attributes.
151
+	 *
152
+	 * @return \Htsl\Parser\Node\TagNode
153
+	 */
154
+	protected function parseNameValue():self
155
+	{
156
+		$params = $this->line->pregGet('/ <(.*?)>(?= |$)/', 1)
157
+		 and $params = preg_split('/(?<!\\\\)\\|/', $params)
158
+		  and array_map(function ($key, $value) {
159
+			  return isset($key) && isset($value) ? $this->setAttribute($key, $this->checkExpression(str_replace('\\|', '|', $value))) : '';
160
+		  }, $this->config['name_value'], $params);
161
+
162
+		return $this;
163
+	}
164
+
165
+	/**
166
+	 * Parsing @links.
167
+	 *
168
+	 * @return \Htsl\Parser\Node\TagNode
169
+	 */
170
+	protected function parseLink():self
171
+	{
172
+		$link = $this->line->pregGet('/ @((?!\()(?:[^ ]| (?=[a-zA-Z0-9]))+|(?<exp>\((?:[^()]+|(?&exp)?)+?\)))(?= |$)/', 1);
173
+
174
+		if (strlen($link)) {
175
+			if (isset($this->config['target']) && ':' === $link[0]) {
176
+				$this->setAttribute($this->config['link'], 'javascript'.$link);
177
+			} elseif ('//' === ($firstTwoLetters = substr($link, 0, 2))) {
178
+				$this->setAttribute($this->config['link'], 'http:'.$link);
179
+			} elseif ('\\\\' === $firstTwoLetters) {
180
+				$this->setAttribute($this->config['link'], 'https://'.substr($link, 2));
181
+			} else {
182
+				$this->setAttribute($this->config['link'], $this->checkExpression($link));
183
+			}
184
+		}
185
+
186
+		return $this;
187
+	}
188
+
189
+	/**
190
+	 * Parsing >target.
191
+	 *
192
+	 * @return \Htsl\Parser\Node\TagNode
193
+	 */
194
+	protected function parseTarget():self
195
+	{
196
+		$target = $this->line->pregGet('/ >((?!\()(?:[^ ]| (?=[a-zA-Z0-9]))+|(?<exp>\((?:[^()]+|(?&exp)?)+?\)))(?= |$)/', 1);
197
+
198
+		if (strlen($target)) {
199
+			$this->setAttribute($this->config['target'], $this->checkExpression($target));
200
+		}
201
+
202
+		return $this;
203
+	}
204
+
205
+	/**
206
+	 * Parsing _placeholders.
207
+	 *
208
+	 * @return \Htsl\Parser\Node\TagNode
209
+	 */
210
+	protected function parseAlt():self
211
+	{
212
+		$alt = $this->line->pregGet('/ _((?!\()(?:[^ ]| (?=[a-zA-Z0-9]))+|(?<exp>\((?:[^()]+|(?&exp)?)+?\)))(?= |$)/', 1);
213
+
214
+		if (strlen($alt)) {
215
+			$this->setAttribute($this->config['alt'], $this->checkExpression($alt));
216
+		}
217
+
218
+		return $this;
219
+	}
220
+
221
+	/**
222
+	 * Parsing #ids .classes ^titles [styles] %event{>listeners<} and {other attributes}.
223
+	 *
224
+	 * @return \Htsl\Parser\Node\TagNode
225
+	 */
226
+	protected function parseCommonAttributes():string
227
+	{
228
+		$attributes = '';
229
+
230
+		$id = $this->line->pregGet('/ #([^ ]+|(?<exp>\((?:[^()]+|(?&exp)?)+?\)))(?= |$)/', 1)
231
+		 and $this->setAttribute('id', $id);
232
+
233
+		$classes = $this->line->pregGet('/ \.[^ ]+(?= |$)/')
234
+		 and preg_match_all('/\.((?(?!\()[^.]+|(?<exp>\((?:[^()]+|(?&exp)?)+?\))))/', $classes, $matches)
235
+		  and $classes = implode(' ', array_filter(array_map(function ($className) {
236
+			  return $this->checkExpression($className);
237
+		  }, $matches[1])))
238
+		   and $this->setAttribute('class', $classes);
239
+
240
+		$title = $this->line->pregGet('/ \^((?!\()(?:[^ ]| (?=[a-zA-Z0-9]))+|(?<exp>\((?:[^()]+|(?&exp)?)+?\)))(?= |$)/', 1)
241
+		 and $this->setAttribute('title', $title);
242
+
243
+		$style = $this->line->pregGet('/ \[([^\]]+;)(?=\]( |$))/', 1)
244
+		 and $this->setAttribute('style', $style);
245
+
246
+		$eventListeners = $this->line->pregMap('/ %(\w+)\{>(.*?)<\}(?= |$)/', function ($string, $name, $code) {
247
+			$this->setAttribute('on'.$name, str_replace('"', '&quot;', $code));
248
+		})
249
+		 and implode('', $eventListeners);
250
+
251
+		$other = $this->line->pregGet('/(?<=\{).*?(?=;\}( |$))/')
252
+		 and array_map(function ($keyValue) {
253
+			 preg_replace_callback('/^([\w-:]+)(?:\?(.+?))?(?:\=(.*))?$/', function ($matches) {
254
+				 $this->setAttribute($matches[1], ($matches[3] ?? $matches[1]) ?: $matches[1], $matches[2] ?? null);
255
+			 }, $keyValue);
256
+		 }, explode(';', $other));
257
+
258
+		return $attributes;
259
+	}
260
+
261
+	/**
262
+	 * Checking and parse PHP expressions.
263
+	 *
264
+	 * @param string $value
265
+	 *
266
+	 * @return string
267
+	 */
268
+	protected function checkExpression(string $value):string
269
+	{
270
+		return preg_match('/^\(.*\)$/', $value) ? '<?='.substr($value, 1, -1).';?>' : str_replace('"', '&quot;', $value);
271
+	}
272
+
273
+	/**
274
+	 * Getting attribute string with HTML syntax.
275
+	 *
276
+	 * @return string
277
+	 */
278
+	protected function getAttributesString():string
279
+	{
280
+		ksort($this->attributes);
281
+
282
+		return implode('', array_map(static function (string $key, array $data) {
283
+			return isset($data['condition']) && strlen($data['condition']) ?
284
+				"<?php if( {$data['condition']} ){?> $key=\"{$data['value']}\"<?php }?>"
285
+				:
286
+				" $key=\"{$data['value']}\"";
287
+		}, array_keys($this->attributes), $this->attributes));
288
+	}
289
+
290
+	/**
291
+	 * Setting attribute.
292
+	 *
293
+	 * @param string      $key       Attribute name.
294
+	 * @param string      $value     Attribute value
295
+	 * @param string|null $condition Optional condition, If given, attribute will seted only when condition is true.
296
+	 */
297
+	protected function setAttribute(string $key, string $value, string $condition = null):self
298
+	{
299
+		if (isset($this->attributes[$key])) {
300
+			$this->document->throw("Attribute $key of $this->name cannot redeclare.");
301
+		}
302
+
303
+		$this->attributes[$key] = [
304
+			'value'     => $value,
305
+			'condition' => $condition,
306
+		];
307
+
308
+		return $this;
309
+	}
310
+
311
+	/*             *\
312 312
        ArrayAccess
313 313
     \*             */
314 314
 
315
-    /**
316
-     * Whether the attribute isset.
317
-     *
318
-     * @param mixed $offset
319
-     *
320
-     * @return bool
321
-     */
322
-    public function offsetExists($offset):bool
323
-    {
324
-        return isset($this->attributes[$offset]);
325
-    }
326
-
327
-    /**
328
-     * Getting attribute with array access.
329
-     *
330
-     * @param mixed $offset
331
-     *
332
-     * @return mixed
333
-     */
334
-    public function offsetGet($offset)
335
-    {
336
-        return $this->attributes[$offset] ?? null;
337
-    }
338
-
339
-    /**
340
-     * Setting Attribute with array access.
341
-     *
342
-     * @param mixed $offset
343
-     * @param mixed $value
344
-     */
345
-    public function offsetSet($offset, $value)
346
-    {
347
-        $this->setAttribute($offset, $value);
348
-    }
349
-
350
-    /**
351
-     * Unset an attribute with array access.
352
-     *
353
-     * @param mixed $offset
354
-     */
355
-    public function offsetUnset($offset)
356
-    {
357
-        if (isset($this->attributes[$offset])) {
358
-            unset($this->attributes[$offset]);
359
-        }
360
-    }
315
+	/**
316
+	 * Whether the attribute isset.
317
+	 *
318
+	 * @param mixed $offset
319
+	 *
320
+	 * @return bool
321
+	 */
322
+	public function offsetExists($offset):bool
323
+	{
324
+		return isset($this->attributes[$offset]);
325
+	}
326
+
327
+	/**
328
+	 * Getting attribute with array access.
329
+	 *
330
+	 * @param mixed $offset
331
+	 *
332
+	 * @return mixed
333
+	 */
334
+	public function offsetGet($offset)
335
+	{
336
+		return $this->attributes[$offset] ?? null;
337
+	}
338
+
339
+	/**
340
+	 * Setting Attribute with array access.
341
+	 *
342
+	 * @param mixed $offset
343
+	 * @param mixed $value
344
+	 */
345
+	public function offsetSet($offset, $value)
346
+	{
347
+		$this->setAttribute($offset, $value);
348
+	}
349
+
350
+	/**
351
+	 * Unset an attribute with array access.
352
+	 *
353
+	 * @param mixed $offset
354
+	 */
355
+	public function offsetUnset($offset)
356
+	{
357
+		if (isset($this->attributes[$offset])) {
358
+			unset($this->attributes[$offset]);
359
+		}
360
+	}
361 361
 }
Please login to merge, or discard this patch.