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