1<?php 2 3namespace dokuwiki\plugin\struct\meta; 4 5/** 6 * Class ConfigParser 7 * 8 * Utilities to parse the configuration syntax into an array 9 * 10 * @package dokuwiki\plugin\struct\meta 11 */ 12class ConfigParser 13{ 14 protected $config = array(); 15 16 /** 17 * Parser constructor. 18 * 19 * parses the given configuration lines 20 * 21 * @param $lines 22 */ 23 public function __construct($lines) 24 { 25 /** @var \helper_plugin_struct_config $helper */ 26 $helper = plugin_load('helper', 'struct_config'); 27 $this->config = array( 28 'limit' => 0, 29 'dynfilters' => false, 30 'summarize' => false, 31 'rownumbers' => false, 32 'sepbyheaders' => false, 33 'target' => '', 34 'align' => array(), 35 'headers' => array(), 36 'cols' => array(), 37 'widths' => array(), 38 'filter' => array(), 39 'schemas' => array(), 40 'sort' => array(), 41 'csv' => true, 42 'nesting' => 0, 43 ); 44 // parse info 45 foreach ($lines as $line) { 46 list($key, $val) = $this->splitLine($line); 47 if (!$key) continue; 48 49 $logic = 'OR'; 50 // handle line commands (we allow various aliases here) 51 switch ($key) { 52 case 'from': 53 case 'schema': 54 case 'tables': 55 $this->config['schemas'] = array_merge($this->config['schemas'], $this->parseSchema($val)); 56 break; 57 case 'select': 58 case 'cols': 59 case 'field': 60 case 'col': 61 $this->config['cols'] = $this->parseValues($val); 62 break; 63 case 'sepbyheaders': 64 $this->config['sepbyheaders'] = (bool)$val; 65 break; 66 case 'head': 67 case 'header': 68 case 'headers': 69 $this->config['headers'] = $this->parseValues($val); 70 break; 71 case 'align': 72 $this->config['align'] = $this->parseAlignments($val); 73 break; 74 case 'width': 75 case 'widths': 76 $this->config['widths'] = $this->parseWidths($val); 77 break; 78 case 'min': 79 $this->config['min'] = abs((int)$val); 80 break; 81 case 'limit': 82 case 'max': 83 $this->config['limit'] = abs((int)$val); 84 break; 85 case 'order': 86 case 'sort': 87 $sorts = $this->parseValues($val); 88 $sorts = array_map(array($helper, 'parseSort'), $sorts); 89 $this->config['sort'] = array_merge($this->config['sort'], $sorts); 90 break; 91 case 'where': 92 case 'filter': 93 case 'filterand': // phpcs:ignore PSR2.ControlStructures.SwitchDeclaration.TerminatingComment 94 /** @noinspection PhpMissingBreakStatementInspection */ 95 case 'and': // phpcs:ignore PSR2.ControlStructures.SwitchDeclaration.TerminatingComment 96 $logic = 'AND'; 97 case 'filteror': 98 case 'or': 99 $flt = $helper->parseFilterLine($logic, $val); 100 if ($flt) { 101 $this->config['filter'][] = $flt; 102 } 103 break; 104 case 'dynfilters': 105 $this->config['dynfilters'] = (bool)$val; 106 break; 107 case 'rownumbers': 108 $this->config['rownumbers'] = (bool)$val; 109 break; 110 case 'summarize': 111 $this->config['summarize'] = (bool)$val; 112 break; 113 case 'csv': 114 $this->config['csv'] = (bool)$val; 115 break; 116 case 'target': 117 case 'page': 118 $this->config['target'] = cleanID($val); 119 break; 120 case 'nesting': 121 case 'nest': 122 $this->config['nesting'] = (int) $val; 123 break; 124 default: 125 $data = array('config' => &$this->config, 'key' => $key, 'val' => $val); 126 $ev = new \Doku_Event('PLUGIN_STRUCT_CONFIGPARSER_UNKNOWNKEY', $data); 127 if ($ev->advise_before()) { 128 throw new StructException("unknown option '%s'", hsc($key)); 129 } 130 $ev->advise_after(); 131 } 132 } 133 134 // fill up headers - a NULL signifies that the column label is wanted 135 $this->config['headers'] = (array)$this->config['headers']; 136 $cnth = count($this->config['headers']); 137 $cntf = count($this->config['cols']); 138 for ($i = $cnth; $i < $cntf; $i++) { 139 $this->config['headers'][] = null; 140 } 141 // fill up alignments 142 $cnta = count($this->config['align']); 143 for ($i = $cnta; $i < $cntf; $i++) { 144 $this->config['align'][] = null; 145 } 146 } 147 148 /** 149 * Get the parsed configuration 150 * 151 * @return array 152 */ 153 public function getConfig() 154 { 155 return $this->config; 156 } 157 158 /** 159 * Splits the given line into key and value 160 * 161 * @param $line 162 * @return array returns ['',''] if the line is empty 163 */ 164 protected function splitLine($line) 165 { 166 // ignore comments 167 $line = preg_replace('/(?<![&\\\\])#.*$/', '', $line); 168 $line = str_replace('\\#', '#', $line); 169 $line = trim($line); 170 if (empty($line)) return ['', '']; 171 172 $line = preg_split('/\s*:\s*/', $line, 2); 173 $line[0] = strtolower($line[0]); 174 if (!isset($line[1])) $line[1] = ''; 175 176 return $line; 177 } 178 179 /** 180 * parses schema config and aliases 181 * 182 * @param $val 183 * @return array 184 */ 185 protected function parseSchema($val) 186 { 187 $schemas = array(); 188 $parts = explode(',', $val); 189 foreach ($parts as $part) { 190 @list($table, $alias) = array_pad(explode(' ', trim($part)), 2, ''); 191 $table = trim($table); 192 $alias = trim($alias); 193 if (!$table) continue; 194 195 $schemas[] = array($table, $alias,); 196 } 197 return $schemas; 198 } 199 200 /** 201 * Parse alignment data 202 * 203 * @param string $val 204 * @return string[] 205 */ 206 protected function parseAlignments($val) 207 { 208 $cols = explode(',', $val); 209 $data = array(); 210 foreach ($cols as $col) { 211 $col = trim(strtolower($col)); 212 if ($col[0] == 'c') { 213 $align = 'center'; 214 } elseif ($col[0] == 'r') { 215 $align = 'right'; 216 } elseif ($col[0] == 'l') { 217 $align = 'left'; 218 } else { 219 $align = null; 220 } 221 $data[] = $align; 222 } 223 224 return $data; 225 } 226 227 /** 228 * Parse width data 229 * 230 * @param $val 231 * @return array 232 */ 233 protected function parseWidths($val) 234 { 235 $vals = explode(',', $val); 236 $vals = array_map('trim', $vals); 237 $len = count($vals); 238 for ($i = 0; $i < $len; $i++) { 239 $val = trim(strtolower($vals[$i])); 240 241 if (preg_match('/^\d+.?(\d+)?(px|em|ex|ch|rem|%|in|cm|mm|q|pt|pc)$/', $val)) { 242 // proper CSS unit? 243 $vals[$i] = $val; 244 } elseif (preg_match('/^\d+$/', $val)) { 245 // decimal only? 246 $vals[$i] = $val . 'px'; 247 } else { 248 // invalid 249 $vals[$i] = ''; 250 } 251 } 252 return $vals; 253 } 254 255 /** 256 * Split values at the commas, 257 * - Wrap with quotes to escape comma, quotes escaped by two quotes 258 * - Within quotes spaces are stored. 259 * 260 * @param string $line 261 * @return array 262 */ 263 protected function parseValues($line) 264 { 265 $values = array(); 266 $inQuote = false; 267 $escapedQuote = false; 268 $value = ''; 269 $len = strlen($line); 270 for ($i = 0; $i < $len; $i++) { 271 if ($line[$i] == '"') { 272 if ($inQuote) { 273 if ($escapedQuote) { 274 $value .= '"'; 275 $escapedQuote = false; 276 continue; 277 } 278 if ($line[$i + 1] == '"') { 279 $escapedQuote = true; 280 continue; 281 } 282 array_push($values, $value); 283 $inQuote = false; 284 $value = ''; 285 continue; 286 } else { 287 $inQuote = true; 288 $value = ''; //don't store stuff before the opening quote 289 continue; 290 } 291 } elseif ($line[$i] == ',') { 292 if ($inQuote) { 293 $value .= ','; 294 continue; 295 } else { 296 if (strlen($value) < 1) { 297 continue; 298 } 299 array_push($values, trim($value)); 300 $value = ''; 301 continue; 302 } 303 } 304 $value .= $line[$i]; 305 } 306 if (strlen($value) > 0) { 307 array_push($values, trim($value)); 308 } 309 return $values; 310 } 311} 312