1<?php
2
3use dokuwiki\Extension\SyntaxPlugin;
4
5/**
6 * DokuWiki Plugin number (Syntax Component)
7 *
8 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
9 * @author Vincent Tscherter <vincent@tscherter.net>
10 */
11class syntax_plugin_number extends SyntaxPlugin
12{
13    /** @inheritDoc */
14    public function getType()
15    {
16        return 'substition';
17    }
18
19    /** @inheritDoc */
20    public function getPType()
21    {
22        return 'normal';
23    }
24
25    /** @inheritDoc */
26    public function getSort()
27    {
28        return 100;
29    }
30
31    /** @inheritDoc */
32    public function connectTo($mode)
33    {
34        $this->Lexer->addSpecialPattern('{{n>.*?}}', $mode, 'plugin_number');
35    }
36
37    /** @inheritDoc */
38    public function handle($match, $state, $pos, Doku_Handler $handler)
39    {
40
41        $raw = substr($match, 4, -2);
42        $raw = strtolower($raw);
43        $raw = preg_replace('/[^0-9a-z_]/', '', $raw);
44
45        $data = [];
46        $data['raw'] = $raw;
47
48        if (preg_match('/^\d+(e\d+)?$/', $raw)) {
49            $data['type'] = 'dec';
50            $data['value'] = intval($raw);
51        } else if (preg_match('/^0b[01]+$/', $raw)) {
52            $data['type'] = 'bin';
53            $data['value'] = intval(substr($raw, 2), 2);
54        } else if (preg_match('/^0x[0-9a-f]+$/i', $raw)) {
55            $data['type'] = 'hex';
56            $data['value'] = intval(substr($raw, 2), 16);
57        } else if (preg_match('/^[0-9a-z]+_[0-9]+$/i', $raw)) {
58            $data['type'] = 'padic';
59            list($number, $base) = explode('_', $raw);
60            $data['value'] = intval($number, $base);
61            $data['number'] = $number;
62            $data['base'] = intval($base);
63            if ($data['base'] < 2 || $data['base'] > 36 || base_convert($data['value'], 10, $data['base']) != $number)
64                $data['value'] = NAN;
65        } else {
66            $data['value'] = NAN;
67        }
68        return $data;
69    }
70
71    static private function renderDec($value)
72    {
73        return '<code style="color: blue">'.$value.'</code>';
74    }
75
76    static private function renderHex($value)
77    {
78        return '<code style="color: blue"><span style="color: red">0x</span>'
79            . (is_string($value) ? substr($value, 2) : dechex($value)) . '</code>';
80    }
81
82    static private function renderBin($value)
83    {
84        return '<code style="color: blue"><span style="color: red">0b</span>'
85            . (is_string($value) ? substr($value, 2) : decbin($value)) . '</code>';
86    }
87
88    const NUMBERSYSTEM = ';;Binary;Ternary;Quaternary;Quinary;Senary;Septenary;Octal;Nonary;Decimal;Undecimal;Duodecimal;Tridecimal;Tetradecimal;Pentadecimal;Hexadecimal;Heptadecimal;Octodecimal;Enneadecimal;Vigesimal;Unvigesimal;Duovigesimal;Trivigesimal;Tetravigesimal;Pentavigesimal;Hexavigesimal;Heptavigesimal;Octovigesimal;Enneavigesimal;Trigesimal;Untrigesimal;Duotrigesimal;Tritrigesimal;Tetratrigesimal;Pentatrigesimal; Hexatrigesimal';
89
90    /** @inheritDoc */
91    public function render($mode, Doku_Renderer $renderer, $data)
92    {
93        if ($mode !== 'xhtml') {
94            return false;
95        }
96        $value = $data['value'];
97        if (is_nan($value)) {
98            $renderer->doc .= '<code style="color: red">⚠️ warning: ' . $data['raw'] . ' is not a valid input</code>';
99            return true;
100        }
101        if (!is_int($value) || $value >= PHP_INT_MAX) {
102            $renderer->doc .= '<code style="color: red">⚠️ warning: ' . $data['raw'] . ' is not a safe for conversion </code>'.$value;
103            return true;
104        }
105        $type = $data['type'];
106        if ($type == 'dec') {
107            $number = self::renderDec($data['raw']);
108            $tooltip = '<strong>Decimal number</strong> (base 10)<br>= '
109                . self::renderBin($value) . ' (binary)';
110            if ("~$value" != '~' . $data['raw']) { //
111                $tooltip .= '<br>= ' . self::renderDec($value) . ' (decimal)';
112            }
113            $tooltip .= '<br>= ' . self::renderHex($value) . ' (hexadecimal)';
114        } else if ($type == 'bin') {
115            $number = self::renderBin($data['raw']);
116            $tooltip = '<strong>Binary number</strong> (base 2)<br>= '
117                . self::renderDec($value) . ' (decimal)<br>=  '
118                . self::renderHex($value) . ' (hexadecimal)';
119        } else if ($type == 'hex') {
120            $number = self::renderHex($data['raw']);
121            $tooltip = '<strong>Hexadecimal number</strong> (base 16)<br>= '
122                . self::renderBin($value) . ' (binary)<br>= '
123                . self::renderDec($value) . ' (decimal)';
124        } else if ($type == 'padic') {
125            $number = '<code style="color: blue"><span style="color: grey">[</span>'
126                . $data['number']
127                . '<span style="color: grey">]</span><sub style="color: red">'
128                . $data['base'] . '</sub></code>';
129            $tooltip = '<strong>'
130                . explode(';', self::NUMBERSYSTEM)[$data['base']]
131                . ' number</strong> (base ' . $data['base'] . ')<br>= '
132                . self::renderBin($value) . ' (binary)<br>= '
133                . self::renderDec($value) . ' (decimal)<br>= '
134                . self::renderHex($value) . ' (hexadecimal)';
135        }
136        //
137        $renderer->doc .= "<span class='plugin-number'><span class='plugin-number-tooltip'>$tooltip</span>$number</span>";
138
139        return true;
140    }
141}