Completed
Push — master ( 602f00...8ac133 )
by stéphane
07:27
created

Builder::buildComment()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 2
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
0 ignored issues
show
Coding Style introduced by
Missing file doc comment
Loading history...
3
namespace Dallgoot\Yaml;
4
5
use Dallgoot\Yaml\{Yaml as Y, Regex as R};
6
7
/**
8
 * Constructs the result (YamlObject or array) according to every Node and respecting value
9
 *
10
 * @author  Stéphane Rebai <[email protected]>
11
 * @license Apache 2.0
12
 * @link    TODO : url to specific online doc
13
 */
0 ignored issues
show
Coding Style introduced by
Missing @category tag in class comment
Loading history...
Coding Style introduced by
Missing @package tag in class comment
Loading history...
14
final class Builder
15
{
16
    private static $_root;
17
    private static $_debug;
18
19
    const ERROR_NO_KEYNAME = self::class.": key has NO IDENTIFIER on line %d";
20
    const INVALID_DOCUMENT = self::class.": DOCUMENT %d can NOT be a mapping AND a sequence";
21
22
    /**
23
     * Generic function to distinguish between Node and NodeList
24
     *
25
     * @param Node|NodeList $node   The node.
26
     * @param mixed         $parent The parent
27
     *
28
     * @return mixed  ( description_of_the_return_value )
29
     */
30
    private static function build(object $node, &$parent = null)
31
    {
32
        if ($node instanceof NodeList) return self::buildNodeList($node, $parent);
33
        return self::buildNode($node, $parent);
34
    }
35
36
    /**
37
     * Builds a node list.
38
     *
39
     * @param NodeList $node   The node
40
     * @param mixed    $parent The parent
41
     *
42
     * @return mixed    The parent (object|array) or a string representing the NodeList.
43
     */
44
    private static function buildNodeList(NodeList $node, &$parent)
0 ignored issues
show
Coding Style introduced by
Private method name "Builder::buildNodeList" must be prefixed with an underscore
Loading history...
45
    {
46
        if ($node->type & (Y::RAW | Y::LITTERALS)) {
47
            return self::buildLitteral($node, $node->type);
0 ignored issues
show
Bug introduced by
It seems like $node->type can also be of type null; however, parameter $type of Dallgoot\Yaml\Builder::buildLitteral() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

47
            return self::buildLitteral($node, /** @scrutinizer ignore-type */ $node->type);
Loading history...
48
        }
49
        $p = $parent;
50
        switch ($node->type) {
51
            case Y::MAPPING: // fall through
52
            case Y::SET:      $p = new \StdClass; break;
0 ignored issues
show
Coding Style introduced by
Closing brace must be on a line by itself
Loading history...
53
            case Y::SEQUENCE: $p = [];break;
0 ignored issues
show
Coding Style introduced by
Closing brace must be on a line by itself
Loading history...
54
        }
55
        $out = null;
56
        foreach ($node as $child) {
57
            $result = self::build($child, $p);
58
            if (!is_null($result)) {
59
                if (is_string($result)) {
60
                    $out .= $result.' ';
61
                } else {
62
                    return $result;
63
                }
64
            }
65
        }
66
        return is_null($out) ? $p : rtrim($out);
67
    }
68
69
    /**
70
     * Builds a node.
71
     *
72
     * @param Node    $node    The node of any Node->type
73
     * @param mixed  $parent  The parent
74
     *
75
     * @return mixed  The node value as scalar, array or object or null to otherwise.
76
     */
77
    private static function buildNode(Node $node, &$parent)
0 ignored issues
show
Coding Style introduced by
Private method name "Builder::buildNode" must be prefixed with an underscore
Loading history...
78
    {
79
        extract((array) $node, EXTR_REFS);
80
        if ($type & (Y::REF_DEF | Y::REF_CALL)) {
81
            if (is_object($value)) {
82
                $tmp = self::build($value, $parent) ?? $parent;
83
            } else {
84
                $tmp = Node2PHP::get($node);
85
            }
86
            if ($type === Y::REF_DEF) self::$_root->addReference($identifier, $tmp);
87
            return self::$_root->getReference($identifier);
88
        }
89
90
        if ($type & Y::COMMENT) self::$_root->addComment($node->line, $node->value);
91
        $typesActions = [Y::DIRECTIVE => 'buildDirective',
92
                         Y::ITEM      => 'buildItem',
93
                         Y::KEY       => 'buildKey',
94
                         Y::SET_KEY   => 'buildSetKey',
95
                         Y::SET_VALUE => 'buildSetValue',
96
                         Y::TAG       => 'buildTag',
97
        ];
98
        if (isset($typesActions[$type])) {
99
            return self::{$typesActions[$type]}($node, $parent);
100
        }
101
        return is_object($value) ? self::build($value, $parent) : Node2PHP::get($node);
102
    }
103
104
    /**
105
     * Builds a key and set the property + value to the given parent
106
     *
107
     * @param Node $node       The node with type YAML::KEY
108
     * @param object|array $parent       The parent
109
     *
110
     * @throws \ParseError if Key has no name(identifier) Note: empty string is allowed
111
     * @return null
112
     */
113
    private static function buildKey(Node $node, &$parent)
0 ignored issues
show
Coding Style introduced by
Private method name "Builder::buildKey" must be prefixed with an underscore
Loading history...
114
    {
115
        extract((array) $node, EXTR_REFS);
116
        if (is_null($identifier)) {
117
            throw new \ParseError(sprintf(self::ERROR_NO_KEYNAME, $line));
118
        } else {
119
            if (is_array($parent)) {
120
                $target = &$parent[$identifier];
121
            } else {
122
                $target = &$parent->{$identifier};
123
            }
124
            if ($value instanceof Node && ($value->type & (Y::KEY|Y::SET_KEY|Y::SET_KEY|Y::ITEM))) {
125
                $target = $value->type & Y::ITEM ? [] : new \StdClass;
126
                self::build($value, $target);
127
            } elseif (is_object($value)) {
128
                if (is_null($value->type) && $value->getTypes() & Y::SCALAR && !($value->getTypes() & Y::COMMENT)) {
129
                    $target = self::buildLitteral($value, Y::LITT_FOLDED);
130
                } else {
131
                    $target = self::build($value, $target);
132
                }
133
            } else {
134
                $target = Node2PHP::get($node);
135
            }
136
        }
137
    }
138
139
    /**
140
     * Builds an item. Adds the item value to the parent array|Iterator
141
     *
142
     * @param      Node        $node    The node with type YAML::ITEM
143
     * @param      array|\Iterator      $parent  The parent
144
     *
145
     * @throws     \Exception  if parent is another type than array or object Iterator
146
     * @return null
147
     */
148
    private static function buildItem(Node $node, &$parent)
0 ignored issues
show
Coding Style introduced by
Private method name "Builder::buildItem" must be prefixed with an underscore
Loading history...
149
    {
150
        if (!is_array($parent) && !($parent instanceof \ArrayIterator)) {
151
            throw new \Exception("parent must be an Iterable not ".(is_object($parent) ? get_class($parent) : gettype($parent)), 1);
152
        }
153
        if ($node->value instanceof Node && $node->value->type & Y::KEY) {
154
            self::build($node->value, $parent);
155
        } else {
156
            $index = count($parent);
157
            $parent[$index] = is_null($node->value) ? null : self::build($node->value, $parent[$index]);
0 ignored issues
show
Bug introduced by
It seems like $node->value can also be of type string; however, parameter $node of Dallgoot\Yaml\Builder::build() does only seem to accept object, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

157
            $parent[$index] = is_null($node->value) ? null : self::build(/** @scrutinizer ignore-type */ $node->value, $parent[$index]);
Loading history...
158
        }
159
    }
160
161
    /**
162
     * Builds a file.  check multiple documents & split if more than one documents
163
     * TODO: implement splitting on YAML::DOC_END also
164
     *
165
     * @param   Node   $_root      The root node : Node with Node->type === YAML::ROOT
166
     * @param   int   $_debug      the level of debugging requested
167
     *
168
     * @return array|YamlObject      list of documents or juste one.
169
     */
170
    public static function buildContent(Node $_root, int $_debug)
171
    {
172
        self::$_debug = $_debug;
173
        $totalDocStart = 0;
174
        $documents = [];
175
        if ($_root->value instanceof Node) {
176
            $q = new NodeList;
177
            $q->push($_root->value);
178
            return self::buildDocument($q, 0);
179
        }
180
        $_root->value->setIteratorMode(NodeList::IT_MODE_DELETE);
0 ignored issues
show
Bug introduced by
The method setIteratorMode() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

180
        $_root->value->/** @scrutinizer ignore-call */ 
181
                       setIteratorMode(NodeList::IT_MODE_DELETE);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
181
        foreach ($_root->value as $child) {
182
            if ($child->type & Y::DOC_START) $totalDocStart++;
183
            //if 0 or 1 DOC_START = we are still in first document
184
            $currentDoc = $totalDocStart > 1 ? $totalDocStart - 1 : 0;
185
            if (!isset($documents[$currentDoc])) $documents[$currentDoc] = new NodeList();
186
            $documents[$currentDoc]->push($child);
187
        }
188
        $content = array_map([self::class, 'buildDocument'], $documents, array_keys($documents));
189
        return count($content) === 1 ? $content[0] : $content;
190
    }
191
192
    /**
193
     * Builds a document. Basically a NodeList of children
194
     *
195
     * @param      NodeList     $list   The list
196
     * @param      integer      $key    The key
197
     *
198
     * @throws     \ParseError  (description)
199
     *
200
     * @return     YamlObject   The document as the separated part (by YAML::DOC_START) inside a whole YAML content
201
     */
202
    private static function buildDocument(NodeList $list, int $key):YamlObject
0 ignored issues
show
Coding Style introduced by
Private method name "Builder::buildDocument" must be prefixed with an underscore
Loading history...
203
    {
204
        self::$_root = new YamlObject();
205
        $childTypes = $list->getTypes();
206
        $isaMapping  = (bool) (Y::KEY | Y::MAPPING) & $childTypes;
207
        $isaSequence = (bool) Y::ITEM & $childTypes;
208
        $isaSet      = (bool) Y::SET_VALUE & $childTypes;
209
        if ($isaMapping && $isaSequence) {
210
            throw new \ParseError(sprintf(self::INVALID_DOCUMENT, $key));
211
        } else {
212
            switch (true) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $isaSet of type integer to the boolean true. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
Bug Best Practice introduced by
It seems like you are loosely comparing $isaSequence of type integer to the boolean true. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
213
                case $isaSequence: $list->type = Y::SEQUENCE;break;
0 ignored issues
show
Coding Style introduced by
Closing brace must be on a line by itself
Loading history...
214
                case $isaSet:      $list->type = Y::SET;break;
0 ignored issues
show
Coding Style introduced by
Closing brace must be on a line by itself
Loading history...
215
                default:           $list->type = Y::MAPPING;
216
            }
217
        }
218
        $string = '';
219
        foreach ($list as $child) {
220
            $result = self::build($child, self::$_root);
221
            if (is_string($result)) {
222
                $string .= $result.' ';
223
            }
224
        }
225
        if (!empty($string)) {
226
            self::$_root->setText(rtrim($string));
227
        }
228
        return self::$_root;
229
    }
230
231
    /**
232
     * Builds a litteral (folded or not) or any NodeList that has YAML::RAW type (like a multiline value)
233
     *
234
     * @param      NodeList  $children  The children
235
     * @param      integer   $type      The type
236
     *
237
     * @return     string    The litteral.
238
     */
239
    private static function buildLitteral(NodeList $children, int $type):string
0 ignored issues
show
Coding Style introduced by
Private method name "Builder::buildLitteral" must be prefixed with an underscore
Loading history...
240
    {
241
        $lines = [];
242
        $children->rewind();
243
        $refIndent = $children->current()->indent;
244
        // $lastChild = $children->pop();
245
        // $hasComment = strpos($lastChild->value, ' #');
246
        // if (is_int($hasComment)) {
247
        //     $children->push(new Node(trim(substr($lastChild->value, 0, $hasComment)), $lastChild->line));
248
        //     self::$_root->addComment($lastChild->line, trim(substr($lastChild->value, $hasComment)));//keep the '#' to note that it is NOT a fulline comment;
249
        // } else {
250
        //     $children->push($lastChild);
251
        // }
252
        //remove trailing blank nodes
253
        $max = $children->count() - 1;
254
        while ($children->offsetGet($max)->type & Y::BLANK) {
255
            $children->offsetUnset($max);
256
            $max = $children->count() - 1;
257
        }
258
        $children->rewind();
259
        // TODO : Example 6.1. Indentation Spaces  spaces must be considered as content
260
        foreach ($children as $child) {
261
            if ($child->value instanceof NodeList) {
262
                $lines[] = self::buildLitteral($child->value, $type);
263
            } else {
264
                $prefix = '';
265
                if ($type & Y::LITT_FOLDED && ($child->indent > $refIndent || ($child->type & Y::BLANK))) {
266
                    $prefix = "\n";
267
                }
268
                if (!($child->type & (Y::SCALAR|Y::BLANK))) {
269
                    switch ($child->type) {
270
                        case Y::ITEM:    $child->value = '- '.$child->value;break;
0 ignored issues
show
Coding Style introduced by
Closing brace must be on a line by itself
Loading history...
271
                        case Y::COMMENT: $child->value = '# '.$child->value;break;
0 ignored issues
show
Coding Style introduced by
Closing brace must be on a line by itself
Loading history...
272
                        default: //die(__METHOD__.Y::getName($child->type));
273
                    }
274
                }
275
                if (is_object($child->value)) {
276
                    // var_dump($child->$value);
277
                    throw new \ParseError(__METHOD__.':'.get_class($child->value), 1);
278
                    // die(__METHOD__);
279
                }
280
                $lines[] = $prefix.$child->value;
281
            }
282
        }
283
        if ($type & Y::RAW)         return implode('',   $lines);
284
        if ($type & Y::LITT)        return implode("\n", $lines);
285
        if ($type & Y::LITT_FOLDED) return preg_replace(['/ +(\n)/','/(\n+) +/'], "$1", implode(' ',  $lines));
286
        return '';
287
    }
288
289
    /**
290
     * Builds a set key.
291
     *
292
     * @param      Node        $node    The node of type YAML::SET_KEY.
293
     * @param      object      $parent  The parent
294
     *
295
     * @throws     \Exception  if a problem occurs during serialisation (json format) of the key
296
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
297
    private function buildSetKey(Node $node, &$parent)
0 ignored issues
show
Coding Style introduced by
Private method name "Builder::buildSetKey" must be prefixed with an underscore
Loading history...
298
    {
299
        $built = self::build($node->value, $parent);
0 ignored issues
show
Bug introduced by
It seems like $node->value can also be of type null and string; however, parameter $node of Dallgoot\Yaml\Builder::build() does only seem to accept object, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

299
        $built = self::build(/** @scrutinizer ignore-type */ $node->value, $parent);
Loading history...
300
        $stringKey = is_string($built) && Regex::isProperlyQuoted($built) ? trim($built, '\'" '): $built;
301
        $key = json_encode($stringKey, JSON_PARTIAL_OUTPUT_ON_ERROR|JSON_UNESCAPED_SLASHES);
302
        // if (empty($key)) throw new \Exception("Cant serialize complex key: ".var_export($node->value, true), 1);
303
        $parent->{trim($key, '\'" ')} = null;
304
    }
305
306
    /**
307
     * Builds a set value.
308
     *
309
     * @param      Node    $node    The node of type YAML::SET_VALUE
310
     * @param      object  $parent  The parent (the document object or any previous object created through a mapping key)
311
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
312
    private function buildSetValue(Node $node, &$parent)
0 ignored issues
show
Coding Style introduced by
Private method name "Builder::buildSetValue" must be prefixed with an underscore
Loading history...
313
    {
314
        $prop = array_keys(get_object_vars($parent));
315
        $key = end($prop);
316
        if ($node->value->type & (Y::ITEM|Y::KEY|Y::SEQUENCE|Y::MAPPING)) {
317
            $p = $node->value->type === Y::ITEM ? [] : new \StdClass;
318
            self::build($node->value, $p);
0 ignored issues
show
Bug introduced by
It seems like $node->value can also be of type null and string; however, parameter $node of Dallgoot\Yaml\Builder::build() does only seem to accept object, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

318
            self::build(/** @scrutinizer ignore-type */ $node->value, $p);
Loading history...
319
        } else {
320
            $p = self::build($node->value, $parent->{$key});
321
        }
322
        $parent->{$key} = $p;
323
    }
324
325
    /**
326
     * Builds a tag and its value (also built) and encapsulates them in a Tag object.
327
     *
328
     * @param      Node    $node    The node of type YAML::TAG
329
     * @param      mixed  $parent  The parent
330
     *
331
     * @return     Tag     The tag object of class Dallgoot\Yaml\Tag.
332
     */
333
    private function buildTag(Node $node, &$parent)
0 ignored issues
show
Coding Style introduced by
Private method name "Builder::buildTag" must be prefixed with an underscore
Loading history...
334
    {
335
        $list = $node->value;
336
        $current = $list->current();
0 ignored issues
show
Bug introduced by
The method current() does not exist on Dallgoot\Yaml\Node. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

336
        /** @scrutinizer ignore-call */ 
337
        $current = $list->current();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method current() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

336
        /** @scrutinizer ignore-call */ 
337
        $current = $list->current();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
337
        if ((is_object($parent) || is_array($parent)) && $current->type & Y::KEY) {
338
            self::buildKey($list, $parent);
339
            return;
340
        }
341
        if ($parent === self::$_root) {
342
            $parent->addTag($node->identifier);
343
            return;
344
        }
345
        //TODO: have somewhere a list of common tags and their treatment
346
        if (in_array($node->identifier, ['!binary', '!str'])) {
347
            // if ($list->value instanceof NodeList) $node->value->value->type = Y::RAW;
348
            // else $node->value->type = Y::RAW;
349
        }
350
        return new Tag($node->identifier, $list->count === 0 ? null : self::buildNodeList($list, $node));
0 ignored issues
show
Bug introduced by
The property count does not seem to exist on Dallgoot\Yaml\Node.
Loading history...
Bug introduced by
The property count does not seem to exist on Dallgoot\Yaml\NodeList.
Loading history...
351
    }
352
353
    /**
354
     * Builds a directive. NOT IMPLEMENTED YET
355
     *
356
     * @param      Node  $node    The node
357
     * @param      mixed  $parent  The parent
358
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
359
    private function buildDirective(Node $node, $parent)
0 ignored issues
show
Coding Style introduced by
Private method name "Builder::buildDirective" must be prefixed with an underscore
Loading history...
360
    {
361
        // TODO : implement
362
    }
363
}
364