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