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