Completed
Push — master ( 1706c7...107e4e )
by Andreas
13:23
created

Engine::getValue()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 3
1
<?php
2
/**
3
 * This file is part of AirTemplate.
4
 *
5
 * (c) 2016 Andreas Blaser
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 *
10
 * @package AirTemplate
11
 * @author  Andreas Blaser <[email protected]>
12
 * @license http://www.spdx.org/licenses/MIT MIT License
13
 */
14
15
namespace AirTemplate;
16
17
/**
18
 * Class Engine
19
 *
20
 * The extended render engine class.
21
 * Supports:
22
 * - Everything from the BaseEngine class
23
 * - Nested templates: {{field|render("sub-template")}}, {{field|each("sub-template")}}
24
 * - Datapath: {{field=rel-path/to/value}}, {{field=/abs-path/to/value}}
25
 */
26
class Engine extends BaseEngine
27
{
28
29
    /**
30
     * Complexity level (0=low, 1=high)
31
     *
32
     * @var int
33
     */
34
    protected $complexity = [];
35
36
    /**
37
     * Array of 'path' arrays pointing to other locations in data.
38
     *
39
     * @var array
40
     */
41
    protected $datapath = [];
42
43
    /**
44
     * Translation table for each separator.
45
     *
46
     * @var array
47
     */
48
    protected $escapeChars = [
49
        '\n' => "\n",
50
        '\r' => "\r",
51
        '\t' => "\t",
52
        '\v' => "\v",
53
        '\e' => "\e",
54
        '\f' => "\f",
55
        '\\\\' => "\\",
56
    ];
57
58
    /**
59
     * Constructor.
60
     *
61
     * @param array $templates    Array of parsed templates
62
     * @param array $fieldOptions Array of field options
63
     */
64
    public function __construct(array $templates, array $fieldOptions)
65
    {
66
        parent::__construct($templates, $fieldOptions);
67
        foreach ($templates as $name => $template) {
68
            $this->datapath[$name] = $template['datapath'];
69
            $this->complexity[$name] = $template['complexity'];
70
        }
71
    }
72
73
    /**
74
     * Merge template and values and return the rendered string. Replacement
75
     * values may be specified as an assoc array or an object.
76
     *
77
     * @param string       $name     Template name
78
     * @param array|object $data     Data object or array
79
     *
80
     * @return string The rendered result
81
     */
82
    protected function merge($name, $data)
83
    {
84
        $result = '';
85
        if ($this->complexity[$name] < 1) {
86
            return parent::merge($name, $data);
87
        }
88
        $this->isObject = is_object($data);
89
        foreach ($this->templates[$name] as $index => $fragment) {
90
            if (!isset($this->fields[$name][$index])) {
91
                $result .= $fragment;
92
                continue;
93
            }
94
            $field = $this->fields[$name][$index];
95
            $value = $this->getValue(
96
                $field,
97
                $data,
98
                $this->datapath[$name][$field]
99
            );
100 View Code Duplication
            if ($this->fieldOptions[$name][$field] !== false) {
101
                $value = $this->renderField(
102
                    $value,
103
                    $field,
104
                    $data,
105
                    $this->fieldOptions[$name][$field]
106
                );
107
            }
108
            $result .= $value;
109
        }
110
        return $result;
111
    }
112
113
    /**
114
     * get the value for a field.
115
     *
116
     * @param string       $field    Field name
117
     * @param array|object $data     Data array or object
118
     * @param array|bool   $datapath A datapath array or false
119
     *
120
     * @return string
121
     */
122
    private function getValue($field, $data, $datapath)
123
    {
124
        return $this->isObject
125
            ? self::getObjectValue($field, $data, $datapath)
126
            : self::getArrayValue($field, $data, $datapath);
127
    }
128
129
    /**
130
     * get the value for a field or an empty string.
131
     *
132
     * @param string     $field    Field name
133
     * @param array      $data     Data array
134
     * @param array|bool $datapath A datapath array or false
135
     *
136
     * @return string
137
     */
138 View Code Duplication
    private static function getArrayValue($field, $data, $datapath)
139
    {
140
        if ($datapath === false && isset($data[$field])) {
141
            return $data[$field];
142
        }
143
        if ($datapath !== false) {
144
            return self::queryArray($datapath, $data);
145
        }
146
        return '';
147
    }
148
149
    /**
150
     * Lookup a value in the data array.
151
     *
152
     * @param array $keys An array of access keys
153
     * @param array $data Data array
154
     *
155
     * @return mixed Field value or empty string
156
     */
157
    private static function queryArray(array $keys, array $data)
158
    {
159
        $result = $data;
160
        foreach ($keys as $key) {
161
            if (!isset($result[$key])) {
162
                return '';
163
            }
164
            $result = &$result[$key];
165
        }
166
        return $result;
167
    }
168
169
    /**
170
     * get the value for a field or an empty string.
171
     *
172
     * @param string     $field    Field name
173
     * @param object     $data     Data object
174
     * @param array|bool $datapath A datapath array or false
175
     *
176
     * @return string
177
     */
178 View Code Duplication
    private static function getObjectValue($field, $data, $datapath)
179
    {
180
        if ($datapath === false && isset($data->$field)) {
181
            return $data->$field;
182
        }
183
        if ($datapath !== false) {
184
            return self::queryObject($datapath, $data);
185
        }
186
        return '';
187
    }
188
189
    /**
190
     * Lookup a value in the data object.
191
     *
192
     * @param array  $keys An array of access keys
193
     * @param object $data Data object
194
     *
195
     * @return string
196
     */
197
    private static function queryObject(array $keys, $data)
198
    {
199
        $result = $data;
200
        foreach ($keys as $key) {
201
            if ($key[0] == '@') {
202
                return self::queryObjectAttr(substr($key, 1), $result);
203
            }
204
            $res = (array) $result;
205
            if (isset($res[$key])) {
206
                $result = &$res[$key];
207
                continue;
208
            }
209
            if (isset($result->{$key})) {
210
                $result = $result->{$key};
211
                continue;
212
            }
213
            return '';
214
        }
215
        return $result;
216
    }
217
218
    /**
219
     * Lookup an attribute value in the data element.
220
     *
221
     * @param string $name    Attribute name
222
     * @param object $element Object property
223
     *
224
     * @return string Attribute value or empty string
225
     */
226
    private static function queryObjectAttr($name, $element)
227
    {
228
        if (is_a($element, 'SimpleXMLElement')) {
229
            $attr = $element->attributes();
230
            if (isset($attr[$name])) {
231
                return (string) $attr[$name];
232
            }
233
        }
234
        return '';
235
    }
236
237
    /**
238
     * Renders the output according to the specified options.
239
     *
240
     * @param mixed        $value    Replacement value
241
     * @param string       $field    Field name
242
     * @param array|object $data     Data array or object
243
     * @param mixed        $options  Fields options
244
     *
245
     * @return string The formatted value
246
     */
247
    protected function renderField(
248
        $value,
249
        $field,
250
        $data,
251
        $options
252
    ) {
253
        foreach ($options as $option) {
254
            if ($option[0] == 'default:' && empty($value)) {
255
                return $option[1];
256
            }
257
            $value = $this->processOption(
258
                $value,
259
                $field,
260
                $data,
261
                $option
262
            );
263
        }
264
        return $value;
265
    }
266
267
    /**
268
     * Apply a single option to the field value.
269
     *
270
     * @param mixed        $value    Replacement value
271
     * @param string       $field    Field name
272
     * @param array|object $data     Data array or object
273
     * @param mixed        $option   A single option
274
     *
275
     * @return string
276
     */
277
    private function processOption(
278
        $value,
279
        $field,
280
        $data,
281
        $option
282
    ) {
283
        switch ($option[0]) {
284
            case 'php:':
285
                return $this->phpFunction($value, $option[1]);
286
            case 'self:':
287
                return $this->renderSubTemplate($value, $data, $option[1]);
288
        }
289
        return $this->userFunction($value, $field, $data, $option);
290
    }
291
292
    /**
293
     * Renders a sub-template.
294
     *
295
     * When the ? parameter is NOT set, we go a level deeper,
296
     * otherwise we stay on the same level and we pass $data
297
     * instead of $value to render or each.
298
     *
299
     * @param mixed        $value  Field value
300
     * @param array|object $data   Data array or object
301
     * @param array        $option A single option
302
     *
303
     * @return string Field value
304
     */
305
    protected function renderSubTemplate($value, $data, $option)
306
    {
307
        if ($option[0] == 'each') {
308
            $sep = '';
309
            if (isset($option[1][1])) {
310
                $sep = strtr($option[1][1], $this->escapeChars);
311
            }
312
            return $this->each($option[1][0], $value, $sep);
313
        }
314
        if (isset($option[1][1]) && trim($option[1][1]) == '?') {
315
            $value = $data;
316
        }
317
        return $this->render($option[1][0], $value);
318
    }
319
}
320