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