[ Index ] |
PHP Cross Reference of phpBB-3.3.2-deutsch |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * @package s9e\TextFormatter 5 * @copyright Copyright (c) 2010-2020 The s9e authors 6 * @license http://www.opensource.org/licenses/mit-license.php The MIT License 7 */ 8 namespace s9e\TextFormatter\Plugins\BBCodes; 9 10 use RuntimeException; 11 use s9e\TextFormatter\Parser\Tag; 12 use s9e\TextFormatter\Plugins\ParserBase; 13 14 class Parser extends ParserBase 15 { 16 /** 17 * @var array Attributes of the BBCode being parsed 18 */ 19 protected $attributes; 20 21 /** 22 * @var array Configuration for the BBCode being parsed 23 */ 24 protected $bbcodeConfig; 25 26 /** 27 * @var string Name of the BBCode being parsed 28 */ 29 protected $bbcodeName; 30 31 /** 32 * @var string Suffix of the BBCode being parsed, including its colon 33 */ 34 protected $bbcodeSuffix; 35 36 /** 37 * @var integer Position of the cursor in the original text 38 */ 39 protected $pos; 40 41 /** 42 * @var integer Position of the start of the BBCode being parsed 43 */ 44 protected $startPos; 45 46 /** 47 * @var string Text being parsed 48 */ 49 protected $text; 50 51 /** 52 * @var integer Length of the text being parsed 53 */ 54 protected $textLen; 55 56 /** 57 * @var string Text being parsed, normalized to uppercase 58 */ 59 protected $uppercaseText; 60 61 /** 62 * {@inheritdoc} 63 */ 64 public function parse($text, array $matches) 65 { 66 $this->text = $text; 67 $this->textLen = strlen($text); 68 $this->uppercaseText = ''; 69 foreach ($matches as $m) 70 { 71 $this->bbcodeName = strtoupper($m[1][0]); 72 if (!isset($this->config['bbcodes'][$this->bbcodeName])) 73 { 74 continue; 75 } 76 $this->bbcodeConfig = $this->config['bbcodes'][$this->bbcodeName]; 77 $this->startPos = $m[0][1]; 78 $this->pos = $this->startPos + strlen($m[0][0]); 79 80 try 81 { 82 $this->parseBBCode(); 83 } 84 catch (RuntimeException $e) 85 { 86 // Do nothing 87 } 88 } 89 } 90 91 /** 92 * Add the end tag that matches current BBCode 93 * 94 * @return Tag 95 */ 96 protected function addBBCodeEndTag() 97 { 98 return $this->parser->addEndTag($this->getTagName(), $this->startPos, $this->pos - $this->startPos); 99 } 100 101 /** 102 * Add the self-closing tag that matches current BBCode 103 * 104 * @return Tag 105 */ 106 protected function addBBCodeSelfClosingTag() 107 { 108 $tag = $this->parser->addSelfClosingTag($this->getTagName(), $this->startPos, $this->pos - $this->startPos); 109 $tag->setAttributes($this->attributes); 110 111 return $tag; 112 } 113 114 /** 115 * Add the start tag that matches current BBCode 116 * 117 * @return Tag 118 */ 119 protected function addBBCodeStartTag() 120 { 121 $tag = $this->parser->addStartTag($this->getTagName(), $this->startPos, $this->pos - $this->startPos); 122 $tag->setAttributes($this->attributes); 123 124 return $tag; 125 } 126 127 /** 128 * Parse the end tag that matches given BBCode name and suffix starting at current position 129 * 130 * @return Tag|null 131 */ 132 protected function captureEndTag() 133 { 134 if (empty($this->uppercaseText)) 135 { 136 $this->uppercaseText = strtoupper($this->text); 137 } 138 $match = '[/' . $this->bbcodeName . $this->bbcodeSuffix . ']'; 139 $endTagPos = strpos($this->uppercaseText, $match, $this->pos); 140 if ($endTagPos === false) 141 { 142 return; 143 } 144 145 return $this->parser->addEndTag($this->getTagName(), $endTagPos, strlen($match)); 146 } 147 148 /** 149 * Get the tag name for current BBCode 150 * 151 * @return string 152 */ 153 protected function getTagName() 154 { 155 // Use the configured tagName if available, or reuse the BBCode's name otherwise 156 return (isset($this->bbcodeConfig['tagName'])) 157 ? $this->bbcodeConfig['tagName'] 158 : $this->bbcodeName; 159 } 160 161 /** 162 * Parse attributes starting at current position 163 * 164 * @return void 165 */ 166 protected function parseAttributes() 167 { 168 $firstPos = $this->pos; 169 $this->attributes = []; 170 while ($this->pos < $this->textLen) 171 { 172 $c = $this->text[$this->pos]; 173 if (strpos(" \n\t", $c) !== false) 174 { 175 ++$this->pos; 176 continue; 177 } 178 if (strpos('/]', $c) !== false) 179 { 180 return; 181 } 182 183 // Capture the attribute name 184 $spn = strspn($this->text, 'abcdefghijklmnopqrstuvwxyz_0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-', $this->pos); 185 if ($spn) 186 { 187 $attrName = strtolower(substr($this->text, $this->pos, $spn)); 188 $this->pos += $spn; 189 if ($this->pos >= $this->textLen) 190 { 191 // The attribute name extends to the end of the text 192 throw new RuntimeException; 193 } 194 if ($this->text[$this->pos] !== '=') 195 { 196 // It's an attribute name not followed by an equal sign, ignore it 197 continue; 198 } 199 } 200 elseif ($c === '=' && $this->pos === $firstPos) 201 { 202 // This is the default param, e.g. [quote=foo] 203 $attrName = (isset($this->bbcodeConfig['defaultAttribute'])) 204 ? $this->bbcodeConfig['defaultAttribute'] 205 : strtolower($this->bbcodeName); 206 } 207 else 208 { 209 throw new RuntimeException; 210 } 211 212 // Move past the = and make sure we're not at the end of the text 213 if (++$this->pos >= $this->textLen) 214 { 215 throw new RuntimeException; 216 } 217 218 $this->attributes[$attrName] = $this->parseAttributeValue(); 219 } 220 } 221 222 /** 223 * Parse the attribute value starting at current position 224 * 225 * @return string 226 */ 227 protected function parseAttributeValue() 228 { 229 // Test whether the value is in quotes 230 if ($this->text[$this->pos] === '"' || $this->text[$this->pos] === "'") 231 { 232 return $this->parseQuotedAttributeValue(); 233 } 234 235 // Capture everything up to whichever comes first: 236 // - an endline 237 // - whitespace followed by a slash and a closing bracket 238 // - a closing bracket, optionally preceded by whitespace 239 // - whitespace followed by another attribute (name followed by equal sign) 240 // 241 // NOTE: this is for compatibility with some forums (such as vBulletin it seems) 242 // that do not put attribute values in quotes, e.g. 243 // [quote=John Smith;123456] (quoting "John Smith" from post #123456) 244 preg_match('((?:[^\\s\\]]|[ \\t](?!\\s*(?:[-\\w]+=|/?\\])))*)', $this->text, $m, null, $this->pos); 245 246 $attrValue = $m[0]; 247 $this->pos += strlen($attrValue); 248 249 return $attrValue; 250 } 251 252 /** 253 * Parse current BBCode 254 * 255 * @return void 256 */ 257 protected function parseBBCode() 258 { 259 $this->parseBBCodeSuffix(); 260 261 // Test whether this is an end tag 262 if ($this->text[$this->startPos + 1] === '/') 263 { 264 // Test whether the tag is properly closed and whether this tag has an identifier. 265 // We skip end tags that carry an identifier because they're automatically added 266 // when their start tag is processed 267 if (substr($this->text, $this->pos, 1) === ']' && $this->bbcodeSuffix === '') 268 { 269 ++$this->pos; 270 $this->addBBCodeEndTag(); 271 } 272 273 return; 274 } 275 276 // Parse attributes 277 $this->parseAttributes(); 278 279 // Test whether the tag is properly closed 280 if (substr($this->text, $this->pos, 1) === ']') 281 { 282 ++$this->pos; 283 } 284 else 285 { 286 // Test whether this is a self-closing tag 287 if (substr($this->text, $this->pos, 2) === '/]') 288 { 289 $this->pos += 2; 290 $this->addBBCodeSelfClosingTag(); 291 } 292 293 return; 294 } 295 296 // Record the names of attributes that need the content of this tag 297 $contentAttributes = []; 298 if (isset($this->bbcodeConfig['contentAttributes'])) 299 { 300 foreach ($this->bbcodeConfig['contentAttributes'] as $attrName) 301 { 302 if (!isset($this->attributes[$attrName])) 303 { 304 $contentAttributes[] = $attrName; 305 } 306 } 307 } 308 309 // Look ahead and parse the end tag that matches this tag, if applicable 310 $requireEndTag = ($this->bbcodeSuffix || !empty($this->bbcodeConfig['forceLookahead'])); 311 $endTag = ($requireEndTag || !empty($contentAttributes)) ? $this->captureEndTag() : null; 312 if (isset($endTag)) 313 { 314 foreach ($contentAttributes as $attrName) 315 { 316 $this->attributes[$attrName] = substr($this->text, $this->pos, $endTag->getPos() - $this->pos); 317 } 318 } 319 elseif ($requireEndTag) 320 { 321 return; 322 } 323 324 // Create this start tag 325 $tag = $this->addBBCodeStartTag(); 326 327 // If an end tag was created, pair it with this start tag 328 if (isset($endTag)) 329 { 330 $tag->pairWith($endTag); 331 } 332 } 333 334 /** 335 * Parse the BBCode suffix starting at current position 336 * 337 * Used to explicitly pair specific tags together, e.g. 338 * [code:123][code]type your code here[/code][/code:123] 339 * 340 * @return void 341 */ 342 protected function parseBBCodeSuffix() 343 { 344 $this->bbcodeSuffix = ''; 345 if ($this->text[$this->pos] === ':') 346 { 347 // Capture the colon and the (0 or more) digits following it 348 $spn = 1 + strspn($this->text, '0123456789', 1 + $this->pos); 349 $this->bbcodeSuffix = substr($this->text, $this->pos, $spn); 350 351 // Move past the suffix 352 $this->pos += $spn; 353 } 354 } 355 356 /** 357 * Parse a quoted attribute value that starts at current offset 358 * 359 * @return string 360 */ 361 protected function parseQuotedAttributeValue() 362 { 363 $quote = $this->text[$this->pos]; 364 $valuePos = $this->pos + 1; 365 do 366 { 367 // Look for the next quote 368 $this->pos = strpos($this->text, $quote, $this->pos + 1); 369 if ($this->pos === false) 370 { 371 // No matching quote. Apparently that string never ends... 372 throw new RuntimeException; 373 } 374 375 // Test for an odd number of backslashes before this character 376 $n = 1; 377 while ($this->text[$this->pos - $n] === '\\') 378 { 379 ++$n; 380 } 381 } 382 while ($n % 2 === 0); 383 384 $attrValue = substr($this->text, $valuePos, $this->pos - $valuePos); 385 if (strpos($attrValue, '\\') !== false) 386 { 387 $attrValue = strtr($attrValue, ['\\\\' => '\\', '\\"' => '"', "\\'" => "'"]); 388 } 389 390 // Skip past the closing quote 391 ++$this->pos; 392 393 return $attrValue; 394 } 395 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Nov 11 20:28:18 2020 | Cross-referenced by PHPXref 0.7.1 |