[ Index ]

PHP Cross Reference of phpBB-3.3.14-deutsch

title

Body

[close]

/vendor/s9e/text-formatter/src/Plugins/BBCodes/ -> Parser.php (source)

   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  }


Generated: Mon Nov 25 19:05:08 2024 Cross-referenced by PHPXref 0.7.1