1<?php 2 3namespace plugin\struct\meta; 4 5/** 6 * Class ConfigParser 7 * 8 * Utilities to parse the configuration syntax into an array 9 * 10 * @package plugin\struct\meta 11 */ 12class ConfigParser { 13 14 15 protected $config = array(); 16 17 /** 18 * Parser constructor. 19 * 20 * parses the given configuration lines 21 * 22 * @param $lines 23 */ 24 public function __construct($lines) { 25 $data = array( 26 'limit' => 0, 27 'dynfilters' => false, 28 'summarize' => false, 29 'rownumbers' => false, 30 'sepbyheaders' => false, 31 'headers' => array(), 32 'widths' => array(), 33 'filter' => array(), 34 'schemas' => array() 35 ); 36 // parse info 37 foreach($lines as $line) { 38 list($key, $val) = $this->splitLine($line); 39 if(!$key) continue; 40 41 $logic = 'OR'; 42 // handle line commands (we allow various aliases here) 43 switch($key) { 44 case 'from': 45 case 'schema': 46 case 'tables': 47 $this->config['schemas'] = array_merge($this->config['schemas'], $this->parseSchema($val)); 48 break; 49 case 'select': 50 case 'cols': 51 case 'field': 52 case 'col': 53 $this->config['cols'] = $this->parseValues($val); 54 break; 55 case 'title': 56 $this->config['title'] = $val; 57 break; 58 case 'head': 59 case 'header': 60 case 'headers': 61 $this->config['headers'] = $this->parseValues($val); 62 break; 63 case 'align': 64 $this->config['align'] = $this->parseAlignments($val); 65 break; 66 case 'widths': 67 $this->config['width'] = $this->parseValues($val); 68 break; 69 case 'min': 70 $this->config['min'] = abs((int) $val); 71 break; 72 case 'limit': 73 case 'max': 74 $this->config['limit'] = abs((int) $val); 75 break; 76 case 'order': 77 case 'sort': 78 // FIXME multiple values!? 79 if(substr($val, 0, 1) == '^') { 80 $this->config['sort'] = array(substr($val, 1), 'DESC'); 81 } else { 82 $this->config['sort'] = array($val, 'ASC'); 83 } 84 break; 85 case 'where': 86 case 'filter': 87 case 'filterand': 88 /** @noinspection PhpMissingBreakStatementInspection */ 89 case 'and': 90 $logic = 'AND'; 91 case 'filteror': 92 case 'or': 93 $flt = $this->parseFilter($val); 94 if($flt) { 95 $flt[] = $logic; 96 $this->config['filter'][] = $flt; 97 } 98 break; 99 case 'page': 100 case 'target': 101 $this->config['page'] = cleanID($val); 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 'sepbyheaders': 113 $this->config['sepbyheaders'] = (bool) $val; 114 break; 115 default: 116 throw new StructException("unknown option '%s'", hsc($val)); 117 } 118 } 119 // we need at least one column to display 120 // fill up headers with field names if necessary 121 $this->config['headers'] = (array) $this->config['headers']; 122 $cnth = count($this->config['headers']); 123 $cntf = count($this->config['cols']); 124 for($i = $cnth; $i < $cntf; $i++) { 125 $column = array_slice($this->config['cols'], $i, 1); 126 $columnprops = array_pop($column); 127 $this->config['headers'][] = $columnprops['title']; 128 } 129 130 return $data; 131 } 132 133 /** 134 * Get the parsed configuration 135 * 136 * @return array 137 */ 138 public function getConfig() { 139 return $this->config; 140 } 141 142 /** 143 * Splits the given line into key and value 144 * 145 * @param $line 146 * @return bool|array returns false for empty lines 147 */ 148 protected function splitLine($line) { 149 // ignore comments 150 $line = preg_replace('/(?<![&\\\\])#.*$/', '', $line); 151 $line = str_replace('\\#', '#', $line); 152 $line = trim($line); 153 if(empty($line)) return false; 154 155 $line = preg_split('/\s*:\s*/', $line, 2); 156 $line[0] = strtolower($line[0]); 157 158 return $line; 159 } 160 161 /** 162 * parses schema config and aliases 163 * 164 * @param $val 165 * @return array 166 */ 167 protected function parseSchema($val){ 168 $schemas = array(); 169 $parts = explode(',', $val); 170 foreach($parts as $part) { 171 list($table, $alias) = explode(' ', $part); 172 $table = trim($table); 173 $alias = trim($alias); 174 if(!$table) continue; 175 176 $schemas[] = array($table, $alias); 177 } 178 return $schemas; 179 } 180 181 /** 182 * Parse a filter 183 * 184 * @param string $val 185 * @return array ($col, $comp, $value) 186 */ 187 protected function parseFilter($val) { 188 189 $comps = Search::COMPARATORS; 190 $comps = array_map('preg_quote_cb', $comps); 191 $comps = join('|', $comps); 192 193 if(!preg_match('/^(.*?)('.$comps.')(.*)$/', $val, $match)) { 194 throw new StructException('Invalid search filter %s', hsc($val)); 195 } 196 array_shift($match); // we don't need the zeroth match 197 return $match; 198 } 199 200 201 /** 202 * Parse alignment data 203 * 204 * @param string $val 205 * @return string[] 206 */ 207 protected function parseAlignments($val) { 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 } else { 217 $align = 'left'; 218 } 219 $data[] = $align; 220 } 221 222 return $data; 223 } 224 225 /** 226 * Split values at the commas, 227 * - Wrap with quotes to escape comma, quotes escaped by two quotes 228 * - Within quotes spaces are stored. 229 * 230 * @param string $line 231 * @return array 232 */ 233 protected function parseValues($line) { 234 $values = array(); 235 $inQuote = false; 236 $escapedQuote = false; 237 $value = ''; 238 $len = strlen($line); 239 for($i = 0; $i < $len; $i++) { 240 if($line{$i} == '"') { 241 if($inQuote) { 242 if($escapedQuote) { 243 $value .= '"'; 244 $escapedQuote = false; 245 continue; 246 } 247 if($line{$i + 1} == '"') { 248 $escapedQuote = true; 249 continue; 250 } 251 array_push($values, $value); 252 $inQuote = false; 253 $value = ''; 254 continue; 255 } else { 256 $inQuote = true; 257 $value = ''; //don't store stuff before the opening quote 258 continue; 259 } 260 } else if($line{$i} == ',') { 261 if($inQuote) { 262 $value .= ','; 263 continue; 264 } else { 265 if(strlen($value) < 1) { 266 continue; 267 } 268 array_push($values, trim($value)); 269 $value = ''; 270 continue; 271 } 272 } 273 $value .= $line{$i}; 274 } 275 if(strlen($value) > 0) { 276 array_push($values, trim($value)); 277 } 278 return $values; 279 } 280 281} 282