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