[ Index ] |
PHP Cross Reference of phpBB-3.3.14-deutsch |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * @package s9e\TextFormatter 5 * @copyright Copyright (c) 2010-2022 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 $prio = ($this->bbcodeSuffix !== '') ? -10 : 0; 122 $tag = $this->parser->addStartTag($this->getTagName(), $this->startPos, $this->pos - $this->startPos, $prio); 123 $tag->setAttributes($this->attributes); 124 125 return $tag; 126 } 127 128 /** 129 * Parse the end tag that matches given BBCode name and suffix starting at current position 130 * 131 * @return Tag|null 132 */ 133 protected function captureEndTag() 134 { 135 if (empty($this->uppercaseText)) 136 { 137 $this->uppercaseText = strtoupper($this->text); 138 } 139 $match = '[/' . $this->bbcodeName . $this->bbcodeSuffix . ']'; 140 $endTagPos = strpos($this->uppercaseText, $match, $this->pos); 141 if ($endTagPos === false) 142 { 143 return; 144 } 145 146 return $this->parser->addEndTag($this->getTagName(), $endTagPos, strlen($match)); 147 } 148 149 /** 150 * Get the tag name for current BBCode 151 * 152 * @return string 153 */ 154 protected function getTagName() 155 { 156 // Use the configured tagName if available, or reuse the BBCode's name otherwise 157 return (isset($this->bbcodeConfig['tagName'])) 158 ? $this->bbcodeConfig['tagName'] 159 : $this->bbcodeName; 160 } 161 162 /** 163 * Parse attributes starting at current position 164 * 165 * @return void 166 */ 167 protected function parseAttributes() 168 { 169 $firstPos = $this->pos; 170 $this->attributes = []; 171 while ($this->pos < $this->textLen) 172 { 173 $c = $this->text[$this->pos]; 174 if (strpos(" \n\t", $c) !== false) 175 { 176 ++$this->pos; 177 continue; 178 } 179 if (strpos('/]', $c) !== false) 180 { 181 return; 182 } 183 184 // Capture the attribute name 185 $spn = strspn($this->text, 'abcdefghijklmnopqrstuvwxyz_0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-', $this->pos); 186 if ($spn) 187 { 188 $attrName = strtolower(substr($this->text, $this->pos, $spn)); 189 $this->pos += $spn; 190 if ($this->pos >= $this->textLen) 191 { 192 // The attribute name extends to the end of the text 193 throw new RuntimeException; 194 } 195 if ($this->text[$this->pos] !== '=') 196 { 197 // It's an attribute name not followed by an equal sign, ignore it 198 continue; 199 } 200 } 201 elseif ($c === '=' && $this->pos === $firstPos) 202 { 203 // This is the default param, e.g. [quote=foo] 204 $attrName = (isset($this->bbcodeConfig['defaultAttribute'])) 205 ? $this->bbcodeConfig['defaultAttribute'] 206 : strtolower($this->bbcodeName); 207 } 208 else 209 { 210 throw new RuntimeException; 211 } 212 213 // Move past the = and make sure we're not at the end of the text 214 if (++$this->pos >= $this->textLen) 215 { 216 throw new RuntimeException; 217 } 218 219 $this->attributes[$attrName] = $this->parseAttributeValue(); 220 } 221 } 222 223 /** 224 * Parse the attribute value starting at current position 225 * 226 * @return string 227 */ 228 protected function parseAttributeValue() 229 { 230 // Test whether the value is in quotes 231 if ($this->text[$this->pos] === '"' || $this->text[$this->pos] === "'") 232 { 233 return $this->parseQuotedAttributeValue(); 234 } 235 236 // Capture everything up to whichever comes first: 237 // - an endline 238 // - whitespace followed by a slash and a closing bracket 239 // - a closing bracket, optionally preceded by whitespace 240 // - whitespace followed by another attribute (name followed by equal sign) 241 // 242 // NOTE: this is for compatibility with some forums (such as vBulletin it seems) 243 // that do not put attribute values in quotes, e.g. 244 // [quote=John Smith;123456] (quoting "John Smith" from post #123456) 245 preg_match('((?:[^\\s\\]]|[ \\t](?!\\s*(?:[-\\w]+=|/?\\])))*)', $this->text, $m, 0, $this->pos); 246 247 $attrValue = $m[0]; 248 $this->pos += strlen($attrValue); 249 250 return $attrValue; 251 } 252 253 /** 254 * Parse current BBCode 255 * 256 * @return void 257 */ 258 protected function parseBBCode() 259 { 260 $this->parseBBCodeSuffix(); 261 262 // Test whether this is an end tag 263 if ($this->text[$this->startPos + 1] === '/') 264 { 265 // Test whether the tag is properly closed and whether this tag has an identifier. 266 // We skip end tags that carry an identifier because they're automatically added 267 // when their start tag is processed 268 if (substr($this->text, $this->pos, 1) === ']' && $this->bbcodeSuffix === '') 269 { 270 ++$this->pos; 271 $this->addBBCodeEndTag(); 272 } 273 274 return; 275 } 276 277 // Parse attributes 278 $this->parseAttributes(); 279 280 // Test whether the tag is properly closed 281 if (substr($this->text, $this->pos, 1) === ']') 282 { 283 ++$this->pos; 284 } 285 else 286 { 287 // Test whether this is a self-closing tag 288 if (substr($this->text, $this->pos, 2) === '/]') 289 { 290 $this->pos += 2; 291 $this->addBBCodeSelfClosingTag(); 292 } 293 294 return; 295 } 296 297 // Record the names of attributes that need the content of this tag 298 $contentAttributes = []; 299 if (isset($this->bbcodeConfig['contentAttributes'])) 300 { 301 foreach ($this->bbcodeConfig['contentAttributes'] as $attrName) 302 { 303 if (!isset($this->attributes[$attrName])) 304 { 305 $contentAttributes[] = $attrName; 306 } 307 } 308 } 309 310 // Look ahead and parse the end tag that matches this tag, if applicable 311 $requireEndTag = ($this->bbcodeSuffix || !empty($this->bbcodeConfig['forceLookahead'])); 312 $endTag = ($requireEndTag || !empty($contentAttributes)) ? $this->captureEndTag() : null; 313 if (isset($endTag)) 314 { 315 foreach ($contentAttributes as $attrName) 316 { 317 $this->attributes[$attrName] = substr($this->text, $this->pos, $endTag->getPos() - $this->pos); 318 } 319 } 320 elseif ($requireEndTag) 321 { 322 return; 323 } 324 325 // Create this start tag 326 $tag = $this->addBBCodeStartTag(); 327 328 // If an end tag was created, pair it with this start tag 329 if (isset($endTag)) 330 { 331 $tag->pairWith($endTag); 332 } 333 } 334 335 /** 336 * Parse the BBCode suffix starting at current position 337 * 338 * Used to explicitly pair specific tags together, e.g. 339 * [code:123][code]type your code here[/code][/code:123] 340 * 341 * @return void 342 */ 343 protected function parseBBCodeSuffix() 344 { 345 $this->bbcodeSuffix = ''; 346 if ($this->text[$this->pos] === ':') 347 { 348 // Capture the colon and the (0 or more) digits following it 349 $spn = 1 + strspn($this->text, '0123456789', 1 + $this->pos); 350 $this->bbcodeSuffix = substr($this->text, $this->pos, $spn); 351 352 // Move past the suffix 353 $this->pos += $spn; 354 } 355 } 356 357 /** 358 * Parse a quoted attribute value that starts at current offset 359 * 360 * @return string 361 */ 362 protected function parseQuotedAttributeValue() 363 { 364 $quote = $this->text[$this->pos]; 365 $valuePos = $this->pos + 1; 366 do 367 { 368 // Look for the next quote 369 $this->pos = strpos($this->text, $quote, $this->pos + 1); 370 if ($this->pos === false) 371 { 372 // No matching quote. Apparently that string never ends... 373 throw new RuntimeException; 374 } 375 376 // Test for an odd number of backslashes before this character 377 $n = 1; 378 while ($this->text[$this->pos - $n] === '\\') 379 { 380 ++$n; 381 } 382 } 383 while ($n % 2 === 0); 384 385 $attrValue = substr($this->text, $valuePos, $this->pos - $valuePos); 386 if (strpos($attrValue, '\\') !== false) 387 { 388 $attrValue = strtr($attrValue, ['\\\\' => '\\', '\\"' => '"', "\\'" => "'"]); 389 } 390 391 // Skip past the closing quote 392 ++$this->pos; 393 394 return $attrValue; 395 } 396 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Mon Nov 25 19:05:08 2024 | Cross-referenced by PHPXref 0.7.1 |