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