1
|
|
|
<?php |
|
|
|
|
2
|
|
|
|
3
|
|
|
namespace Htsl\Parser; |
4
|
|
|
|
5
|
|
|
use Htsl\Htsl; |
6
|
|
|
use Htsl\ReadingBuffer\Contracts\ABuffer as Buffer; |
7
|
|
|
use Htsl\ReadingBuffer\Line; |
8
|
|
|
use Htsl\Helper\TGetter; |
9
|
|
|
use Htsl\Helper\IConfigProvider; |
10
|
|
|
use Htsl\Parser\Node\TagNode; |
11
|
|
|
use Htsl\Parser\Node\StringNode; |
12
|
|
|
use Htsl\Parser\Node\CommentNode; |
13
|
|
|
use Htsl\Parser\Node\ControlNode; |
14
|
|
|
use Htsl\Parser\Node\SectionNode; |
15
|
|
|
use Htsl\Parser\Node\NamelessSectionNode; |
16
|
|
|
use Htsl\Parser\Node\Contracts\ANode as Node; |
17
|
|
|
|
18
|
|
|
//////////////////////////////////////////////////////////////// |
19
|
|
|
|
20
|
|
|
/** |
21
|
|
|
* @property-read string $content Result of document executing. |
22
|
|
|
* @property-read string $doctype Type of this document. |
23
|
|
|
* @property-read string|bool $indentation Indentation of document. |
24
|
|
|
* @property-read \Htsl\Parser\Node\Contracts\ANode|bool $scope Current scope on top of stack. |
25
|
|
|
* @property-read \Htsl\Htsl $htsl Htsl main object of document. |
26
|
|
|
* @property-read int $indentLevel Current indent level. |
27
|
|
|
*/ |
28
|
|
|
class Document implements IConfigProvider |
29
|
|
|
{ |
30
|
|
|
use TGetter; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* Htsl main object owns this document. |
34
|
|
|
* |
35
|
|
|
* @access private |
36
|
|
|
* |
37
|
|
|
* @var \Htsl\Htsl |
38
|
|
|
*/ |
39
|
|
|
private $htsl; |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* Parent document. |
43
|
|
|
* |
44
|
|
|
* @access private |
45
|
|
|
* |
46
|
|
|
* @var \Htsl\Parser\Document | null |
47
|
|
|
*/ |
48
|
|
|
private $parent; |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* Reading buffer of this document. |
52
|
|
|
* |
53
|
|
|
* @access private |
54
|
|
|
* |
55
|
|
|
* @var \Htsl\ReadingBuffer\Contracts\ABuffer |
56
|
|
|
*/ |
57
|
|
|
private $buffer; |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* The indentation and whether output with linefeed and indent. |
61
|
|
|
* |
62
|
|
|
* @access private |
63
|
|
|
* |
64
|
|
|
* @var string | bool |
65
|
|
|
*/ |
66
|
|
|
private $indentation; |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* Type of this document. |
70
|
|
|
* |
71
|
|
|
* @access private |
72
|
|
|
* |
73
|
|
|
* @var string |
74
|
|
|
*/ |
75
|
|
|
private $docType; |
76
|
|
|
|
77
|
|
|
/** |
78
|
|
|
* Current embead script. |
79
|
|
|
* |
80
|
|
|
* @access private |
81
|
|
|
* |
82
|
|
|
* @var \Htsl\Embedment\Contract\Embedment |
83
|
|
|
*/ |
84
|
|
|
private $embedment; |
85
|
|
|
|
86
|
|
|
/** |
87
|
|
|
* Current indent level. |
88
|
|
|
* |
89
|
|
|
* @access private |
90
|
|
|
* |
91
|
|
|
* @var int |
92
|
|
|
*/ |
93
|
|
|
private $level= 0; |
94
|
|
|
|
95
|
|
|
/** |
96
|
|
|
* Section indent level. |
97
|
|
|
* |
98
|
|
|
* @access private |
99
|
|
|
* |
100
|
|
|
* @var int |
101
|
|
|
*/ |
102
|
|
|
private $sectionLevel= 0; |
103
|
|
|
|
104
|
|
|
/** |
105
|
|
|
* Opened nodes. |
106
|
|
|
* |
107
|
|
|
* @access private |
108
|
|
|
* |
109
|
|
|
* @var [ Htsl\Parser\Node\Contracts\ANode, ] |
110
|
|
|
*/ |
111
|
|
|
private $openedNodes= []; |
112
|
|
|
|
113
|
|
|
/** |
114
|
|
|
* Current scopes. |
115
|
|
|
* |
116
|
|
|
* @access private |
117
|
|
|
* |
118
|
|
|
* @var [ Htsl\Parser\Node\Contracts\ANode, ] |
119
|
|
|
*/ |
120
|
|
|
private $scopes= []; |
121
|
|
|
|
122
|
|
|
/** |
123
|
|
|
* Current line number. |
124
|
|
|
* |
125
|
|
|
* @access private |
126
|
|
|
* |
127
|
|
|
* @var int |
128
|
|
|
*/ |
129
|
|
|
private $lineNumber= 0; |
130
|
|
|
|
131
|
|
|
/** |
132
|
|
|
* Current line. |
133
|
|
|
* |
134
|
|
|
* @access private |
135
|
|
|
* |
136
|
|
|
* @var \Htsl\ReadingBuffer\Line |
137
|
|
|
*/ |
138
|
|
|
private $currentLine; |
139
|
|
|
|
140
|
|
|
/** |
141
|
|
|
* Sections that can be show. |
142
|
|
|
* |
143
|
|
|
* @access private |
144
|
|
|
* |
145
|
|
|
* @var [ Htsl\Parser\Section, ] |
146
|
|
|
*/ |
147
|
|
|
private $sections=[]; |
148
|
|
|
|
149
|
|
|
/** |
150
|
|
|
* Whether the document is executed. |
151
|
|
|
* |
152
|
|
|
* @access private |
153
|
|
|
* |
154
|
|
|
* @var bool |
155
|
|
|
*/ |
156
|
|
|
private $isExecuted; |
157
|
|
|
|
158
|
|
|
/** |
159
|
|
|
* Current Section. |
160
|
|
|
* |
161
|
|
|
* @access private |
162
|
|
|
* |
163
|
|
|
* @var \Htsl\Parser\Section |
164
|
|
|
*/ |
165
|
|
|
private $currentSection; |
166
|
|
|
|
167
|
|
|
/** |
168
|
|
|
* The content of this document. |
169
|
|
|
* |
170
|
|
|
* @access private |
171
|
|
|
* |
172
|
|
|
* @var string |
173
|
|
|
*/ |
174
|
|
|
private $content; |
175
|
|
|
|
176
|
|
|
/** |
177
|
|
|
* Constructor of the Document. |
178
|
|
|
* |
179
|
|
|
* @access public |
180
|
|
|
* |
181
|
|
|
* @param \Htsl\Htsl $htsl |
182
|
|
|
* @param \Htsl\ReadingBuffer\Contracts\ABuffer $buffer |
183
|
|
|
* @param \Htsl\Parser\Document | null $parent |
184
|
|
|
*/ |
185
|
|
|
public function __construct( Htsl$htsl, Buffer$buffer, self$parent=null ) |
186
|
|
|
{ |
187
|
|
|
$this->htsl= $htsl; |
188
|
|
|
$this->buffer= $buffer; |
189
|
|
|
|
190
|
|
|
if( $parent ){ |
191
|
|
|
$this->parent= $parent; |
192
|
|
|
$this->docType= $parent->docType; |
193
|
|
|
$this->indentation= $parent->indentation; |
194
|
|
|
}else{ |
195
|
|
|
$this->parseFirstLine(); |
196
|
|
|
} |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
/** |
200
|
|
|
* Executing the document. |
201
|
|
|
* |
202
|
|
|
* @access public |
203
|
|
|
* |
204
|
|
|
* @return \Htsl\Parser\Document |
205
|
|
|
*/ |
206
|
|
|
public function execute():self |
207
|
|
|
{ |
208
|
|
|
if( $this->isExecuted ){ |
209
|
|
|
return $this; |
210
|
|
|
} |
211
|
|
|
return $this->lineByLine() |
212
|
|
|
->bubbleSections() |
213
|
|
|
; |
214
|
|
|
} |
215
|
|
|
|
216
|
|
|
/** |
217
|
|
|
* Alias of getContent. |
218
|
|
|
* |
219
|
|
|
* @access public |
220
|
|
|
* |
221
|
|
|
* @return string |
222
|
|
|
*/ |
223
|
|
|
public function __toString():string |
224
|
|
|
{ |
225
|
|
|
return $this->getContent(); |
226
|
|
|
} |
227
|
|
|
|
228
|
|
|
/** |
229
|
|
|
* Getting the result of document executing. |
230
|
|
|
* |
231
|
|
|
* @access protected |
232
|
|
|
* |
233
|
|
|
* @return string |
234
|
|
|
*/ |
235
|
|
|
protected function getContent():string |
236
|
|
|
{ |
237
|
|
|
if( $this->parent ){ |
238
|
|
|
return $this->execute()->parent->getContent(); |
239
|
|
|
}else{ |
240
|
|
|
return $this->execute()->content; |
241
|
|
|
} |
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
/** |
245
|
|
|
* Getting the next line. |
246
|
|
|
* |
247
|
|
|
* @access protected |
248
|
|
|
* |
249
|
|
|
* @return \Htsl\ReadingBuffer\Line |
250
|
|
|
*/ |
251
|
|
|
protected function getLine():Line |
252
|
|
|
{ |
253
|
|
|
do{ |
254
|
|
|
$line= $this->buffer->getLine(); |
255
|
|
|
}while( $line->isEmpty() && $line->hasMore() ); |
256
|
|
|
|
257
|
|
|
return $line; |
258
|
|
|
} |
259
|
|
|
|
260
|
|
|
/** |
261
|
|
|
* Getting the config of type of this document. |
262
|
|
|
* |
263
|
|
|
* @access public |
264
|
|
|
* |
265
|
|
|
* @param [ string, ] ...$keys |
266
|
|
|
* |
267
|
|
|
* @return mixed |
268
|
|
|
*/ |
269
|
|
|
public function getConfig( string...$keys ) |
270
|
|
|
{ |
271
|
|
|
return $this->htsl->getConfig(array_shift($keys),$this->docType,...$keys); |
272
|
|
|
} |
273
|
|
|
|
274
|
|
|
/** |
275
|
|
|
* Getting the type of this document. |
276
|
|
|
* |
277
|
|
|
* @access public |
278
|
|
|
* |
279
|
|
|
* @return string |
280
|
|
|
*/ |
281
|
|
|
public function getDoctype():string |
282
|
|
|
{ |
283
|
|
|
return $this->docType; |
284
|
|
|
} |
285
|
|
|
|
286
|
|
|
/** |
287
|
|
|
* Getting the indentation. |
288
|
|
|
* |
289
|
|
|
* @access public |
290
|
|
|
* |
291
|
|
|
* @return string | bool |
292
|
|
|
*/ |
293
|
|
|
public function getIndentation() |
294
|
|
|
{ |
295
|
|
|
return $this->indentation; |
296
|
|
|
} |
297
|
|
|
|
298
|
|
|
/** |
299
|
|
|
* Getting the indent level. |
300
|
|
|
* |
301
|
|
|
* @access public |
302
|
|
|
* |
303
|
|
|
* @return int |
304
|
|
|
*/ |
305
|
|
|
public function getIndentLevel():int |
306
|
|
|
{ |
307
|
|
|
return $this->level; |
308
|
|
|
} |
309
|
|
|
|
310
|
|
|
/** |
311
|
|
|
* Parsing the first line. |
312
|
|
|
* |
313
|
|
|
* @access protected |
314
|
|
|
* |
315
|
|
|
* @return \Htsl\Parser\Document |
316
|
|
|
*/ |
317
|
|
|
protected function parseFirstLine():self |
318
|
|
|
{ |
319
|
|
|
$line= $this->getLine(); |
320
|
|
|
|
321
|
|
|
if( '@'===$line->getChar(0) ){ |
322
|
|
|
return $this->setExtending($line); |
323
|
|
|
} |
324
|
|
|
|
325
|
|
|
$this->docType= $line->content; |
326
|
|
|
$docTypeContent= $this->getConfig('doc_types') or $this->throw("DocType $this->docType is not supported"); |
327
|
|
|
|
328
|
|
|
$this->indentation= $this->htsl->getConfig('indentation',$this->docType) ?? ( function( $scalarOrFalse ){ return is_scalar($scalarOrFalse)?$scalarOrFalse:false; } )($this->htsl->getConfig('indentation')); |
329
|
|
|
|
330
|
|
|
$this->appendLine($docTypeContent); |
331
|
|
|
|
332
|
|
|
return $this; |
333
|
|
|
} |
334
|
|
|
|
335
|
|
|
/** |
336
|
|
|
* Setting that this document extends another document. |
337
|
|
|
* |
338
|
|
|
* @access protected |
339
|
|
|
* |
340
|
|
|
* @param \Htsl\ReadingBuffer\Line $firstLine |
341
|
|
|
* |
342
|
|
|
* @return \Htsl\Parser\Document |
343
|
|
|
*/ |
344
|
|
|
protected function setExtending( Line$firstLine ):self |
345
|
|
|
{ |
346
|
|
|
switch( $name= $firstLine->pregGet('/(?<=^@)[\w-:]+/') ){ |
347
|
|
|
default:{ |
348
|
|
|
$this->throw("The @$name is not supported."); |
349
|
|
|
}break; |
350
|
|
|
case 'extend':{ |
351
|
|
|
$this->extend($firstLine->pregGet('/(?<=\( ).*(?= \))/')); |
352
|
|
|
}break; |
353
|
|
|
case 'show': |
354
|
|
|
case 'include': |
355
|
|
|
case 'section':{ |
356
|
|
|
$this->throw("The @$name can not be used on first line."); |
357
|
|
|
}break; |
358
|
|
|
} |
359
|
|
|
|
360
|
|
|
return $this; |
|
|
|
|
361
|
|
|
} |
362
|
|
|
|
363
|
|
|
/** |
364
|
|
|
* Parsing this document line by line. |
365
|
|
|
* |
366
|
|
|
* @access protected |
367
|
|
|
* |
368
|
|
|
* @return \Htsl\Parser\Document |
369
|
|
|
*/ |
370
|
|
|
protected function lineByLine():self |
371
|
|
|
{ |
372
|
|
|
while( ($line= $this->getLine())->hasMore() ){ |
373
|
|
|
$this->lineNumber+= 1; |
374
|
|
|
|
375
|
|
|
if( $this->embedment ){ |
376
|
|
|
$this->embeddingParse($line); |
377
|
|
|
}else{ |
378
|
|
|
$this->parseLine($line); |
379
|
|
|
} |
380
|
|
|
} |
381
|
|
|
|
382
|
|
|
$this->embedment and $this->breakEmbedding(); |
383
|
|
|
|
384
|
|
|
$this->closeNodes($this->level); |
385
|
|
|
|
386
|
|
|
$this->isExecuted= true; |
387
|
|
|
|
388
|
|
|
return $this; |
389
|
|
|
} |
390
|
|
|
|
391
|
|
|
/** |
392
|
|
|
* Parsing embedded line. |
393
|
|
|
* |
394
|
|
|
* @access protected |
395
|
|
|
* |
396
|
|
|
* @param \Htsl\ReadingBuffer\Line $line |
397
|
|
|
* |
398
|
|
|
* @return \Htsl\Parser\Document |
399
|
|
|
*/ |
400
|
|
|
protected function embeddingParse( Line$line ):self |
401
|
|
|
{ |
402
|
|
|
if( $line->content==='<}' ){ |
403
|
|
|
$this->breakEmbedding(); |
404
|
|
|
}else{ |
405
|
|
|
$this->embedment->parseLine($line->getSubIndentLine()); |
406
|
|
|
} |
407
|
|
|
return $this; |
408
|
|
|
} |
409
|
|
|
|
410
|
|
|
/** |
411
|
|
|
* Starting the embedding. |
412
|
|
|
* |
413
|
|
|
* @access protected |
414
|
|
|
* |
415
|
|
|
* @param string $embedType |
416
|
|
|
* |
417
|
|
|
* @return \Htsl\Parser\Document |
418
|
|
|
*/ |
419
|
|
|
protected function startEmbedding( string$embedType ):self |
420
|
|
|
{ |
421
|
|
|
$embedmentClass= '\\Htsl\\Embedment\\'.ucfirst($embedType).'Embedment'; |
422
|
|
|
class_exists($embedmentClass) or $this->throw("Embed type $embedType not exists."); |
423
|
|
|
|
424
|
|
|
$this->embedment= new $embedmentClass($this); |
425
|
|
|
|
426
|
|
|
return $this; |
427
|
|
|
} |
428
|
|
|
|
429
|
|
|
/** |
430
|
|
|
* Ending the embedding. |
431
|
|
|
* |
432
|
|
|
* @access public |
433
|
|
|
* |
434
|
|
|
* @return \Htsl\Parser\Document |
435
|
|
|
*/ |
436
|
|
|
public function breakEmbedding():self |
437
|
|
|
{ |
438
|
|
|
$this->append($this->embedment->getContent()); |
439
|
|
|
$this->embedment= null; |
440
|
|
|
|
441
|
|
|
return $this; |
442
|
|
|
} |
443
|
|
|
|
444
|
|
|
/** |
445
|
|
|
* Parsing line. |
446
|
|
|
* |
447
|
|
|
* @access protected |
448
|
|
|
* |
449
|
|
|
* @param \Htsl\ReadingBuffer\Line $line |
450
|
|
|
* |
451
|
|
|
* @return \Htsl\Parser\Document |
452
|
|
|
*/ |
453
|
|
|
protected function parseLine( Line$line ):self |
454
|
|
|
{ |
455
|
|
|
$this->currentLine= $line; |
456
|
|
|
$this->setLevel($line->getIndentLevel()); |
457
|
|
|
|
458
|
|
|
$this->{'parse'.ucfirst($this->getLineType($line))}($line); |
459
|
|
|
|
460
|
|
|
return $this; |
461
|
|
|
} |
462
|
|
|
|
463
|
|
|
/** |
464
|
|
|
* Getting line type by analyzing first or first two characters of line. |
465
|
|
|
* |
466
|
|
|
* @access protected |
467
|
|
|
* |
468
|
|
|
* @param \Htsl\ReadingBuffer\Line $line |
469
|
|
|
* |
470
|
|
|
* @return string |
471
|
|
|
*/ |
472
|
|
|
protected function getLineType( Line$line ):string |
473
|
|
|
{ |
474
|
|
|
$possibleType= self::POSSIBLE_LINE_TYPES; |
475
|
|
|
|
476
|
|
|
for( $i=0; is_array($possibleType); ++$i ){ |
477
|
|
|
$possibleType= $possibleType[$line->getChar($i)]??$possibleType[' ']; |
478
|
|
|
} |
479
|
|
|
|
480
|
|
|
return $possibleType; |
481
|
|
|
} |
482
|
|
|
|
483
|
|
|
/** |
484
|
|
|
* Possible line types. |
485
|
|
|
* |
486
|
|
|
* @access public |
487
|
|
|
* |
488
|
|
|
* @return array |
489
|
|
|
* |
490
|
|
|
* @todo Make this const private when php 7.1 |
491
|
|
|
*/ |
492
|
|
|
const POSSIBLE_LINE_TYPES= [ |
493
|
|
|
'`'=> [ |
494
|
|
|
'='=> 'expressionHtmlLine', |
495
|
|
|
' '=> 'htmlLine', |
496
|
|
|
], |
497
|
|
|
'='=> 'expressionLine', |
498
|
|
|
'!'=> 'commentLine', |
499
|
|
|
'-'=> 'tagLine', |
500
|
|
|
'~'=> 'controlLine', |
501
|
|
|
'@'=> 'docControlLine', |
502
|
|
|
' '=> 'stringLine', |
503
|
|
|
]; |
504
|
|
|
|
505
|
|
|
/** |
506
|
|
|
* Parsing line as HTML content. |
507
|
|
|
* |
508
|
|
|
* @access protected |
509
|
|
|
* |
510
|
|
|
* @param \Htsl\ReadingBuffer\Line $line |
511
|
|
|
* |
512
|
|
|
* @return \Htsl\Parser\Document |
513
|
|
|
*/ |
514
|
|
View Code Duplication |
protected function parseHtmlLine( Line$line ):self |
|
|
|
|
515
|
|
|
{ |
516
|
|
|
$node= new StringNode($this,$line); |
517
|
|
|
|
518
|
|
|
$this->openNode($node); |
519
|
|
|
|
520
|
|
|
$this->appendLine($line->slice(1)); |
521
|
|
|
|
522
|
|
|
return $this; |
523
|
|
|
} |
524
|
|
|
|
525
|
|
|
/** |
526
|
|
|
* Parsing line as string content. |
527
|
|
|
* |
528
|
|
|
* @access protected |
529
|
|
|
* |
530
|
|
|
* @param \Htsl\ReadingBuffer\Line $line |
531
|
|
|
* |
532
|
|
|
* @return \Htsl\Parser\Document |
533
|
|
|
*/ |
534
|
|
|
protected function parseStringLine( Line$line ):self |
535
|
|
|
{ |
536
|
|
|
$node= new StringNode($this,$line); |
537
|
|
|
|
538
|
|
|
$this->openNode($node); |
539
|
|
|
|
540
|
|
|
$this->appendLine($this->htmlEntities(trim($line->getContent()))); |
541
|
|
|
|
542
|
|
|
return $this; |
543
|
|
|
} |
544
|
|
|
|
545
|
|
|
/** |
546
|
|
|
* Parsing line as PHP expression. |
547
|
|
|
* |
548
|
|
|
* @access protected |
549
|
|
|
* |
550
|
|
|
* @param \Htsl\ReadingBuffer\Line $line |
551
|
|
|
* |
552
|
|
|
* @return \Htsl\Parser\Document |
553
|
|
|
*/ |
554
|
|
|
protected function parseExpressionLine( Line$line ):self |
555
|
|
|
{ |
556
|
|
|
$node= new StringNode($this,$line); |
557
|
|
|
|
558
|
|
|
$this->openNode($node); |
559
|
|
|
|
560
|
|
|
$content= $line->slice(1); |
561
|
|
|
$ent_flag= var_export($this->htsl->getConfig('ENT_flags',$this->docType),true); |
562
|
|
|
$charset= var_export($this->htsl->getConfig('charset'),true); |
563
|
|
|
|
564
|
|
|
$this->appendLine("<?=htmlentities($content,$ent_flag,$charset,false)?>"); |
565
|
|
|
|
566
|
|
|
return $this; |
567
|
|
|
} |
568
|
|
|
|
569
|
|
|
/** |
570
|
|
|
* Parsing line as PHP expression with HTML result. |
571
|
|
|
* |
572
|
|
|
* @access protected |
573
|
|
|
* |
574
|
|
|
* @param \Htsl\ReadingBuffer\Line $line |
575
|
|
|
* |
576
|
|
|
* @return \Htsl\Parser\Document |
577
|
|
|
*/ |
578
|
|
View Code Duplication |
protected function parseExpressionHtmlLine( Line$line ):self |
|
|
|
|
579
|
|
|
{ |
580
|
|
|
$node= new StringNode($this,$line); |
581
|
|
|
|
582
|
|
|
$this->openNode($node); |
583
|
|
|
|
584
|
|
|
$content= $line->slice(1); |
585
|
|
|
|
586
|
|
|
$this->appendLine("<?$content?>"); |
587
|
|
|
|
588
|
|
|
return $this; |
589
|
|
|
} |
590
|
|
|
|
591
|
|
|
/** |
592
|
|
|
* Parsing line as comment. |
593
|
|
|
* |
594
|
|
|
* @access protected |
595
|
|
|
* |
596
|
|
|
* @param \Htsl\ReadingBuffer\Line $line |
597
|
|
|
* |
598
|
|
|
* @return \Htsl\Parser\Document |
599
|
|
|
*/ |
600
|
|
|
protected function parseCommentLine( Line$line ):self |
601
|
|
|
{ |
602
|
|
|
$node= new CommentNode($this,$line); |
603
|
|
|
|
604
|
|
|
$this->openNode($node); |
605
|
|
|
|
606
|
|
|
$this->appendLine($node->open()); |
607
|
|
|
|
608
|
|
|
return $this; |
609
|
|
|
} |
610
|
|
|
|
611
|
|
|
/** |
612
|
|
|
* Parsing line as HTSL tag. |
613
|
|
|
* |
614
|
|
|
* @access protected |
615
|
|
|
* |
616
|
|
|
* @param \Htsl\ReadingBuffer\Line $line |
617
|
|
|
* |
618
|
|
|
* @return \Htsl\Parser\Document |
619
|
|
|
*/ |
620
|
|
|
protected function parseTagLine( Line$line ):self |
621
|
|
|
{ |
622
|
|
|
$tag= new TagNode($this,$line); |
623
|
|
|
|
624
|
|
|
$this->appendLine($tag->open()); |
625
|
|
|
|
626
|
|
|
$tag->embed and $this->startEmbedding($tag->embed); |
627
|
|
|
|
628
|
|
|
$this->openNode($tag); |
629
|
|
|
|
630
|
|
|
return $this; |
631
|
|
|
} |
632
|
|
|
|
633
|
|
|
/** |
634
|
|
|
* Parsing line as control node of Htsl.php. |
635
|
|
|
* |
636
|
|
|
* @access protected |
637
|
|
|
* |
638
|
|
|
* @param \Htsl\ReadingBuffer\Line $line |
639
|
|
|
* |
640
|
|
|
* @return \Htsl\Parser\Document |
641
|
|
|
*/ |
642
|
|
|
protected function parseControlLine( Line$line ):self |
643
|
|
|
{ |
644
|
|
|
$controlStructure= new ControlNode($this,$line); |
645
|
|
|
|
646
|
|
|
$this->appendLine($controlStructure->open()); |
647
|
|
|
|
648
|
|
|
$this->openNode($controlStructure); |
649
|
|
|
|
650
|
|
|
return $this; |
651
|
|
|
} |
652
|
|
|
|
653
|
|
|
/** |
654
|
|
|
* Parsing line as document control node of Htsl.php. |
655
|
|
|
* |
656
|
|
|
* @access protected |
657
|
|
|
* |
658
|
|
|
* @param \Htsl\ReadingBuffer\Line $line |
659
|
|
|
* |
660
|
|
|
* @return \Htsl\Parser\Document |
661
|
|
|
*/ |
662
|
|
|
protected function parseDocControlLine( Line$line ):self |
663
|
|
|
{ |
664
|
|
|
switch( $name= $line->pregGet('/(?<=^@)[\w-:]+/') ){ |
665
|
|
|
default:{ |
666
|
|
|
$this->throw("The @$name is not supported."); |
667
|
|
|
}break; |
668
|
|
|
case 'extend':{ |
669
|
|
|
$this->throw('The @extend can only be used on first line.'); |
670
|
|
|
}break; |
671
|
|
|
case 'include':{ |
672
|
|
|
$this->include($line); |
673
|
|
|
}break; |
674
|
|
|
case 'section':{ |
675
|
|
|
$this->defineSection($line); |
676
|
|
|
}break; |
677
|
|
|
case 'show':{ |
678
|
|
|
$this->showSection($line); |
679
|
|
|
}break; |
680
|
|
|
} |
681
|
|
|
|
682
|
|
|
return $this; |
683
|
|
|
} |
684
|
|
|
|
685
|
|
|
/** |
686
|
|
|
* Parsing extending defination. |
687
|
|
|
* |
688
|
|
|
* @access protected |
689
|
|
|
* |
690
|
|
|
* @param string $fileName |
691
|
|
|
* |
692
|
|
|
* @return \Htsl\Parser\Document |
693
|
|
|
*/ |
694
|
|
|
protected function extend( string$fileName ):self |
695
|
|
|
{ |
696
|
|
|
$this->parent= new static($this->htsl,$this->buffer->goSide($fileName)); |
697
|
|
|
|
698
|
|
|
$this->docType= $this->parent->docType; |
699
|
|
|
$this->indentation= $this->parent->indentation; |
700
|
|
|
|
701
|
|
|
return $this; |
702
|
|
|
} |
703
|
|
|
|
704
|
|
|
/** |
705
|
|
|
* Include another document. |
706
|
|
|
* |
707
|
|
|
* @access protected |
708
|
|
|
* |
709
|
|
|
* @param \Htsl\ReadingBuffer\Line $line |
710
|
|
|
* |
711
|
|
|
* @return \Htsl\Parser\Document |
712
|
|
|
*/ |
713
|
|
|
protected function include( Line$line ):self |
714
|
|
|
{ |
715
|
|
|
$inclued= (new static($this->htsl,$this->buffer->goSide($line->pregGet('/(?<=\( ).*(?= \))/')),$this))->execute()->content; |
|
|
|
|
716
|
|
|
|
717
|
|
View Code Duplication |
if( false!==$this->indentation ){ |
|
|
|
|
718
|
|
|
$inclued= preg_replace('/(?<=^|\\n)(?!$)/',str_repeat($this->indentation,$this->level-$this->sectionLevel),$inclued); |
719
|
|
|
} |
720
|
|
|
|
721
|
|
|
$node= new StringNode($this,$line); |
722
|
|
|
|
723
|
|
|
$this->openNode($node); |
724
|
|
|
|
725
|
|
|
$this->append($inclued); |
726
|
|
|
|
727
|
|
|
return $this; |
728
|
|
|
} |
729
|
|
|
|
730
|
|
|
/** |
731
|
|
|
* Starting to define a section. |
732
|
|
|
* |
733
|
|
|
* @access protected |
734
|
|
|
* |
735
|
|
|
* @param \Htsl\ReadingBuffer\Line $line |
736
|
|
|
* |
737
|
|
|
* @return \Htsl\Parser\Document |
738
|
|
|
*/ |
739
|
|
|
protected function defineSection( Line$line ):self |
740
|
|
|
{ |
741
|
|
|
$node= new SectionNode($this,$line); |
742
|
|
|
|
743
|
|
|
$node->open(); |
744
|
|
|
|
745
|
|
|
$this->openNode($node); |
746
|
|
|
|
747
|
|
|
return $this; |
748
|
|
|
} |
749
|
|
|
|
750
|
|
|
/** |
751
|
|
|
* Showing a section. |
752
|
|
|
* |
753
|
|
|
* @access protected |
754
|
|
|
* |
755
|
|
|
* @param \Htsl\ReadingBuffer\Line $line |
756
|
|
|
* |
757
|
|
|
* @return \Htsl\Parser\Document |
758
|
|
|
*/ |
759
|
|
|
protected function showSection( Line$line ):self |
760
|
|
|
{ |
761
|
|
|
$sectionName= $line->pregGet('/(?<=\( ).*(?= \))/'); |
762
|
|
|
|
763
|
|
|
if( !isset($this->sections[$sectionName]) ){ |
764
|
|
|
$this->openNode(new StringNode($this,$line)); |
765
|
|
|
|
766
|
|
|
return $this; |
767
|
|
|
} |
768
|
|
|
$content= $this->sections[$sectionName]->content; |
769
|
|
|
|
770
|
|
View Code Duplication |
if( false!==$this->indentation ){ |
|
|
|
|
771
|
|
|
$content= preg_replace('/(?<=^|\\n)(?!$)/',str_repeat($this->indentation,$this->level),$content); |
772
|
|
|
} |
773
|
|
|
|
774
|
|
|
$this->append($content); |
775
|
|
|
|
776
|
|
|
$node= new NamelessSectionNode($this,$line); |
777
|
|
|
|
778
|
|
|
$node->open(); |
779
|
|
|
|
780
|
|
|
$this->openNode($node); |
781
|
|
|
|
782
|
|
|
return $this; |
783
|
|
|
} |
784
|
|
|
|
785
|
|
|
/** |
786
|
|
|
* Setting document as section definer. |
787
|
|
|
* |
788
|
|
|
* @access public |
789
|
|
|
* |
790
|
|
|
* @param Section | null $section |
791
|
|
|
*/ |
792
|
|
|
public function setSection( Section$section=null ):self |
793
|
|
|
{ |
794
|
|
|
if( !$section ){ |
795
|
|
|
$this->sectionLevel= 0; |
796
|
|
|
$this->currentSection= null; |
797
|
|
|
|
798
|
|
|
return $this; |
799
|
|
|
} |
800
|
|
|
|
801
|
|
|
if( $this->currentSection ){ |
802
|
|
|
$this->throw('Nesting definition of section is forbidden.'); |
803
|
|
|
} |
804
|
|
|
|
805
|
|
|
if( isset($this->parent->sections[$section->name]) ){ |
806
|
|
|
$this->throw("Section {$section->name} already defined."); |
807
|
|
|
} |
808
|
|
|
|
809
|
|
|
$this->currentSection= $section; |
810
|
|
|
|
811
|
|
|
if( $section->name ){ |
812
|
|
|
$this->parent->sections[$section->name]=$section; |
813
|
|
|
} |
814
|
|
|
|
815
|
|
|
$this->sectionLevel= $this->level+1; |
816
|
|
|
|
817
|
|
|
return $this; |
818
|
|
|
} |
819
|
|
|
|
820
|
|
|
/** |
821
|
|
|
* Bubble the sections to parent document. |
822
|
|
|
* |
823
|
|
|
* @access protected |
824
|
|
|
* |
825
|
|
|
* @return \Htsl\Parser\Document |
826
|
|
|
*/ |
827
|
|
|
protected function bubbleSections():self |
828
|
|
|
{ |
829
|
|
|
if( $this->parent ){ |
830
|
|
|
foreach( $this->sections as $name=>$section ){ |
831
|
|
|
if( !isset($this->parent->sections[$name]) ){ |
832
|
|
|
$this->parent->sections[$name]=$section; |
833
|
|
|
}; |
834
|
|
|
} |
835
|
|
|
} |
836
|
|
|
|
837
|
|
|
return $this; |
838
|
|
|
} |
839
|
|
|
|
840
|
|
|
/** |
841
|
|
|
* Escaping characters to HTML entities. |
842
|
|
|
* |
843
|
|
|
* @access public |
844
|
|
|
* |
845
|
|
|
* @param string $input |
846
|
|
|
* |
847
|
|
|
* @return string |
848
|
|
|
*/ |
849
|
|
|
public function htmlEntities( string$input ):string |
850
|
|
|
{ |
851
|
|
|
return htmlentities($input,$this->htsl->getConfig('ENT_flags',$this->docType),$this->htsl->getConfig('charset'),false); |
852
|
|
|
} |
853
|
|
|
|
854
|
|
|
/** |
855
|
|
|
* Setting indent level of this document. |
856
|
|
|
* |
857
|
|
|
* @access protected |
858
|
|
|
* |
859
|
|
|
* @param int $level |
860
|
|
|
*/ |
861
|
|
|
protected function setLevel( int$level ):self |
862
|
|
|
{ |
863
|
|
|
$level-= $this->level; |
864
|
|
|
|
865
|
|
|
if( $level<=0 ){ |
866
|
|
|
$this->closeNodes(-$level); |
867
|
|
|
}elseif( $level==1 ){ |
868
|
|
|
$this->level+= 1; |
869
|
|
|
}else{ |
870
|
|
|
$this->throw('Indent error.'); |
871
|
|
|
} |
872
|
|
|
|
873
|
|
|
return $this; |
874
|
|
|
} |
875
|
|
|
|
876
|
|
|
/** |
877
|
|
|
* Opening a node. |
878
|
|
|
* |
879
|
|
|
* @access protected |
880
|
|
|
* |
881
|
|
|
* @param \Htsl\Parser\Node\Contracts\ANode $node |
882
|
|
|
* |
883
|
|
|
* @return \Htsl\Parser\Document |
884
|
|
|
*/ |
885
|
|
|
protected function openNode( Node$node ):self |
886
|
|
|
{ |
887
|
|
|
array_push($this->openedNodes,$node); |
888
|
|
|
|
889
|
|
|
$node->scope and $this->setScope($node); |
890
|
|
|
|
891
|
|
|
return $this; |
892
|
|
|
} |
893
|
|
|
|
894
|
|
|
/** |
895
|
|
|
* Closing open node or nodes. |
896
|
|
|
* |
897
|
|
|
* @access protected |
898
|
|
|
* |
899
|
|
|
* @param int $level |
900
|
|
|
* |
901
|
|
|
* @return \Htsl\Parser\Document |
902
|
|
|
*/ |
903
|
|
|
protected function closeNodes( int$level=0 ):self |
904
|
|
|
{ |
905
|
|
|
if( empty($this->openedNodes) ) return $this; |
906
|
|
|
|
907
|
|
|
while( $level-->=0 ){ |
908
|
|
|
$node= array_pop($this->openedNodes); |
909
|
|
|
|
910
|
|
|
$node->scope and $this->removeScope($node); |
911
|
|
|
|
912
|
|
|
$closer=$node->close($this->currentLine) and $this->appendLine($closer); |
913
|
|
|
|
914
|
|
|
$this->level-= $level>=0 ?1:0; |
915
|
|
|
} |
916
|
|
|
|
917
|
|
|
return $this; |
918
|
|
|
} |
919
|
|
|
|
920
|
|
|
/** |
921
|
|
|
* Pushing a scope to stack. |
922
|
|
|
* |
923
|
|
|
* @access protected |
924
|
|
|
* |
925
|
|
|
* @param \Htsl\Parser\Node\Contracts\ANode $scope |
926
|
|
|
*/ |
927
|
|
|
protected function setScope( Node$scope ):int |
928
|
|
|
{ |
929
|
|
|
return array_unshift($this->scopes,$scope); |
930
|
|
|
} |
931
|
|
|
|
932
|
|
|
/** |
933
|
|
|
* Getting current scope on top of stack. |
934
|
|
|
* |
935
|
|
|
* @access public |
936
|
|
|
* |
937
|
|
|
* @return \Htsl\Parser\Node\Contracts\ANode | null |
938
|
|
|
*/ |
939
|
|
|
public function getScope() |
940
|
|
|
{ |
941
|
|
|
return $this->scopes[0]??null; |
942
|
|
|
} |
943
|
|
|
|
944
|
|
|
/** |
945
|
|
|
* Pop a scope from stack. |
946
|
|
|
* |
947
|
|
|
* @access protected |
948
|
|
|
* |
949
|
|
|
* @param \Htsl\Parser\Node\Contracts\ANode $scope |
950
|
|
|
* |
951
|
|
|
* @return \Htsl\Parser\Document |
952
|
|
|
*/ |
953
|
|
|
protected function removeScope( Node$scope ):self |
954
|
|
|
{ |
955
|
|
|
if( $scope!==array_shift($this->scopes) ){ |
956
|
|
|
$this->throw('Scope nesting error'); |
957
|
|
|
}; |
958
|
|
|
|
959
|
|
|
return $this; |
960
|
|
|
} |
961
|
|
|
|
962
|
|
|
/** |
963
|
|
|
* Appending a line of content to parsing result. |
964
|
|
|
* |
965
|
|
|
* @access protected |
966
|
|
|
* |
967
|
|
|
* @param string $content |
968
|
|
|
* |
969
|
|
|
* @return \Htsl\Parser\Document |
970
|
|
|
*/ |
971
|
|
|
protected function appendLine( string$content ):self |
972
|
|
|
{ |
973
|
|
View Code Duplication |
if( false!==$this->indentation ){ |
|
|
|
|
974
|
|
|
$content= str_repeat($this->indentation,$this->level-$this->sectionLevel).$content."\n"; |
975
|
|
|
} |
976
|
|
|
|
977
|
|
|
return $this->append($content); |
978
|
|
|
} |
979
|
|
|
|
980
|
|
|
/** |
981
|
|
|
* Appending some content to parsing result. |
982
|
|
|
* |
983
|
|
|
* @access protected |
984
|
|
|
* |
985
|
|
|
* @param string $content |
986
|
|
|
* |
987
|
|
|
* @return \Htsl\Parser\Document |
988
|
|
|
*/ |
989
|
|
|
protected function append( string$content ):self |
990
|
|
|
{ |
991
|
|
|
if( $this->currentSection ){ |
992
|
|
|
$this->currentSection->append($content); |
993
|
|
|
}else{ |
994
|
|
|
$this->content.=$content; |
995
|
|
|
} |
996
|
|
|
|
997
|
|
|
return $this; |
998
|
|
|
} |
999
|
|
|
|
1000
|
|
|
/** |
1001
|
|
|
* Getting the Htsl main object. |
1002
|
|
|
* |
1003
|
|
|
* @access public |
1004
|
|
|
* |
1005
|
|
|
* @return \Htsl\Htsl |
1006
|
|
|
*/ |
1007
|
|
|
public function getHtsl() |
1008
|
|
|
{ |
1009
|
|
|
return $this->htsl; |
1010
|
|
|
} |
1011
|
|
|
|
1012
|
|
|
/** |
1013
|
|
|
* Throw exception with document name and line number. |
1014
|
|
|
* |
1015
|
|
|
* @access public |
1016
|
|
|
* |
1017
|
|
|
* @param string $message |
1018
|
|
|
* |
1019
|
|
|
* @throw \Htsl\Parser\HtslParsingException |
1020
|
|
|
*/ |
1021
|
|
|
public function throw( string$message ) |
1022
|
|
|
{ |
1023
|
|
|
throw new HtslParsingException("$message at file {$this->buffer->fileName} line $this->lineNumber"); |
|
|
|
|
1024
|
|
|
} |
1025
|
|
|
} |
1026
|
|
|
|
The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.
The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.
To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.