1<?php 2 3declare(strict_types=1); 4 5/* 6 * This file is part of the league/commonmark package. 7 * 8 * (c) Colin O'Dell <colinodell@gmail.com> 9 * 10 * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) 11 * - (c) John MacFarlane 12 * 13 * For the full copyright and license information, please view the LICENSE 14 * file that was distributed with this source code. 15 */ 16 17namespace League\CommonMark\Util; 18 19/** 20 * @psalm-immutable 21 */ 22final class Html5EntityDecoder 23{ 24 /** 25 * @psalm-pure 26 */ 27 public static function decode(string $entity): string 28 { 29 if (\substr($entity, -1) !== ';') { 30 return $entity; 31 } 32 33 if (\substr($entity, 0, 2) === '&#') { 34 if (\strtolower(\substr($entity, 2, 1)) === 'x') { 35 return self::fromHex(\substr($entity, 3, -1)); 36 } 37 38 return self::fromDecimal(\substr($entity, 2, -1)); 39 } 40 41 return \html_entity_decode($entity, \ENT_QUOTES | \ENT_HTML5, 'UTF-8'); 42 } 43 44 /** 45 * @param mixed $number 46 * 47 * @psalm-pure 48 */ 49 private static function fromDecimal($number): string 50 { 51 // Only convert code points within planes 0-2, excluding NULL 52 // phpcs:ignore Generic.PHP.ForbiddenFunctions.Found 53 if (empty($number) || $number > 0x2FFFF) { 54 return self::fromHex('fffd'); 55 } 56 57 $entity = '&#' . $number . ';'; 58 59 $converted = \mb_decode_numericentity($entity, [0x0, 0x2FFFF, 0, 0xFFFF], 'UTF-8'); 60 61 if ($converted === $entity) { 62 return self::fromHex('fffd'); 63 } 64 65 return $converted; 66 } 67 68 /** 69 * @psalm-pure 70 */ 71 private static function fromHex(string $hexChars): string 72 { 73 return self::fromDecimal(\hexdec($hexChars)); 74 } 75} 76