[ Index ]

PHP Cross Reference of phpBB-3.3.2-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-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  }


Generated: Wed Nov 11 20:28:18 2020 Cross-referenced by PHPXref 0.7.1