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