xref: /plugin/struct/meta/ConfigParser.php (revision 8925ba298f12cecb837c2c249b3a592d289eabe2)
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                    throw new StructException("unknown option '%s'", hsc($key));
119            }
120        }
121
122        // fill up headers - a NULL signifies that the column label is wanted
123        $this->config['headers'] = (array) $this->config['headers'];
124        $cnth = count($this->config['headers']);
125        $cntf = count($this->config['cols']);
126        for($i = $cnth; $i < $cntf; $i++) {
127            $this->config['headers'][] = null;
128        }
129        // fill up alignments
130        $cnta = count($this->config['align']);
131        for($i = $cnta; $i < $cntf; $i++) {
132            $this->config['align'][] = null;
133        }
134    }
135
136    /**
137     * Get the parsed configuration
138     *
139     * @return array
140     */
141    public function getConfig() {
142        return $this->config;
143    }
144
145    /**
146     * Splits the given line into key and value
147     *
148     * @param $line
149     * @return bool|array returns false for empty lines
150     */
151    protected function splitLine($line) {
152        // ignore comments
153        $line = preg_replace('/(?<![&\\\\])#.*$/', '', $line);
154        $line = str_replace('\\#', '#', $line);
155        $line = trim($line);
156        if(empty($line)) return false;
157
158        $line = preg_split('/\s*:\s*/', $line, 2);
159        $line[0] = strtolower($line[0]);
160
161        return $line;
162    }
163
164    /**
165     * parses schema config and aliases
166     *
167     * @param $val
168     * @return array
169     */
170    protected function parseSchema($val) {
171        $schemas = array();
172        $parts = explode(',', $val);
173        foreach($parts as $part) {
174            list($table, $alias) = explode(' ', trim($part));
175            $table = trim($table);
176            $alias = trim($alias);
177            if(!$table) continue;
178
179            $schemas[] = array($table, $alias,);
180        }
181        return $schemas;
182    }
183
184    /**
185     * Parse alignment data
186     *
187     * @param string $val
188     * @return string[]
189     */
190    protected function parseAlignments($val) {
191        $cols = explode(',', $val);
192        $data = array();
193        foreach($cols as $col) {
194            $col = trim(strtolower($col));
195            if($col[0] == 'c') {
196                $align = 'center';
197            } elseif($col[0] == 'r') {
198                $align = 'right';
199            } elseif($col[0] == 'l') {
200                $align = 'left';
201            } else {
202                $align = null;
203            }
204            $data[] = $align;
205        }
206
207        return $data;
208    }
209
210    /**
211     * Parse width data
212     *
213     * @param $val
214     * @return array
215     */
216    protected function parseWidths($val) {
217        $vals = explode(',', $val);
218        $vals = array_map('trim', $vals);
219        $len = count($vals);
220        for($i = 0; $i < $len; $i++) {
221            $val = trim(strtolower($vals[$i]));
222
223            if(preg_match('/^\d+.?(\d+)?(px|em|ex|ch|rem|%|in|cm|mm|q|pt|pc)$/', $val)) {
224                // proper CSS unit?
225                $vals[$i] = $val;
226            } else if(preg_match('/^\d+$/', $val)) {
227                // decimal only?
228                $vals[$i] = $val . 'px';
229            } else {
230                // invalid
231                $vals[$i] = '';
232            }
233        }
234        return $vals;
235    }
236
237    /**
238     * Split values at the commas,
239     * - Wrap with quotes to escape comma, quotes escaped by two quotes
240     * - Within quotes spaces are stored.
241     *
242     * @param string $line
243     * @return array
244     */
245    protected function parseValues($line) {
246        $values = array();
247        $inQuote = false;
248        $escapedQuote = false;
249        $value = '';
250        $len = strlen($line);
251        for($i = 0; $i < $len; $i++) {
252            if($line{$i} == '"') {
253                if($inQuote) {
254                    if($escapedQuote) {
255                        $value .= '"';
256                        $escapedQuote = false;
257                        continue;
258                    }
259                    if($line{$i + 1} == '"') {
260                        $escapedQuote = true;
261                        continue;
262                    }
263                    array_push($values, $value);
264                    $inQuote = false;
265                    $value = '';
266                    continue;
267                } else {
268                    $inQuote = true;
269                    $value = ''; //don't store stuff before the opening quote
270                    continue;
271                }
272            } else if($line{$i} == ',') {
273                if($inQuote) {
274                    $value .= ',';
275                    continue;
276                } else {
277                    if(strlen($value) < 1) {
278                        continue;
279                    }
280                    array_push($values, trim($value));
281                    $value = '';
282                    continue;
283                }
284            }
285            $value .= $line{$i};
286        }
287        if(strlen($value) > 0) {
288            array_push($values, trim($value));
289        }
290        return $values;
291    }
292
293}
294