1<?php
2
3class action_plugin_mobiletable extends DokuWiki_Action_Plugin {
4
5    function __construct() {
6        $this->token = '~%'.uniqid().'%~';
7    }
8
9
10    function register(Doku_Event_Handler $controller) {
11        $controller->register_hook(
12            'DOKUWIKI_STARTED',
13            'AFTER',
14            $this,
15            'js_conf'
16        );
17        $controller->register_hook(
18            'PARSER_WIKITEXT_PREPROCESS',
19            'BEFORE',
20            $this,
21            'transform'
22        );
23    }
24
25
26    // Pass configuration to JS
27    function js_conf(&$event, $param) {
28        global $JSINFO;
29        $JSINFO['plugin_mobiletable_hideHeadings'] = array_map(
30            'trim',
31            explode(',', $this->getConf('hideHeadings'))
32        );
33    }
34
35
36    // Find all tables to rewrite.
37    function transform(&$event, $param) {
38        // Find all tables marked with "!".
39        $event->data = preg_replace_callback(
40            '/^!(\^.+)\r?\n((?:^[\|\^].+\r?\n)+)/m',
41            [$this, 'wrap_table'],
42            $event->data
43        );
44    }
45
46
47    // Wrap a table in <mobiletable> syntax.
48    private function wrap_table($m) {
49        list($schema, $i) = $this->schema($m[1]);
50        return "<mobiletable".($i > -1 ? " ".($i + 1) : "").">\n"
51            .str_replace('^!', '^', $m[1])."\n".$m[2]
52            ."\n</mobiletable>\n";
53    }
54
55
56    // Rearrange tables
57    private function rearrange($m) {
58        list($schema, $i) = $this->schema($m[1]);
59        $table = $this->table($schema, $i, $m[2]);
60        // Put everything together.
61        return "<only mobile>\n"
62            .$table
63            ."\n</only>\n<only desktop>\n"
64            .str_replace('^!', '^', $m[1])."\n".$m[2]
65            ."\n</only>\n";
66    }
67
68
69    // Extract the table schema.
70    private function schema($row) {
71        $row = $this->mask($row);
72        $schema = [];
73        $index = -1;
74        $last = '';
75        foreach (explode('^', substr(trim($row), 1, -1)) as $th) {
76            // Check for the index column
77            if (substr($th, 0, 1) == '!') {
78                $index = count($schema);
79                $th = substr($th, 1);
80            }
81            // If non-empty, add to the schema, else take the previous item.
82            $schema[] = $last = trim($th) ?: $last;
83        }
84        return [$schema, $index];
85    }
86
87
88    // Build the mobile table.
89    private function table($schema, $index, $body) {
90        $body = $this->mask($body);
91        $table = '';
92        $length = count($schema);
93        foreach (explode("\n", $body) as $line) {
94            if (substr_count($line, '^') == $length + 1) {
95                // A random header row appeared!
96                $table .= '^'.str_replace('^', '', $line)."^^\n";
97                continue;
98            }
99            // A row with only connected cells.
100            if (preg_match('/^\|[\|\s:]*?([^\|]*)[\|\s:]*\|$/', $line, $text)) {
101                $table .= "|{$text[1]}||\n";
102                continue;
103            }
104            $row = preg_split('/\||\^/', substr($line, 1, -1));
105            // An actual row.
106            if ($index > -1 && isset($row[$index])) {
107                $table .= '^'.$row[$index]."^^\n";
108            }
109            for ($i = 0; $i < $length; $i++) {
110                if ($i != $index) {
111                    $row[$i] = $row[$i] === '' ? ':::' : $row[$i];
112                    $table .= "|{$schema[$i]}|{$row[$i]}|\n";
113                }
114            }
115        }
116        return $this->unmask($table);
117    }
118
119
120    // Mask "|" characters in images and links.
121    private function mask($str) {
122        $str = str_replace("\r", '', trim($str, "\n"));
123        // Remove anchors.
124        $str = preg_replace('/\{\{#:.*?\}\}/', '', $str);
125        // Remove footnotes.
126        $str = preg_replace('/\(\(.*?\)\)/', '', $str);
127        $mask = '$1'.$this->token.'$2';
128        // images
129        $str = preg_replace('/(\{\{[^\}]+)\|([^\|\}]*?\}\})/', $mask, $str);
130        // links
131        $str = preg_replace('/(\[\[[^\]]+)\|([^\|\]]*?\]\])/', $mask, $str);
132        return $str;
133    }
134
135
136    // Unmask "|" characters.
137    private function unmask($str) {
138        return str_replace($this->token, '|', $str);
139    }
140
141}
142