xref: /plugin/struct/meta/ConfigParser.php (revision 8f396942602f641f936f70fc50fd81b8c9564903)
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