Completed
Push — master ( c45521...844759 )
by stéphane
08:22
created

Node::build()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Dallgoot\Yaml;
4
5
/**
6
 *
7
 * @author  Stéphane Rebai <[email protected]>
8
 * @license Apache 2.0
9
 * @link    TODO : url to specific online doc
10
 */
11
class Node
12
{
13
    /** @var bool */
14
    public static $dateAsString = false;
15
16
    /** @var null|string|boolean */
17
    public $identifier;
18
    /** @var int */
19
    public $indent = -1;
20
    /** @var int */
21
    public $line;
22
    /** @var null|string */
23
    public $raw;
24
    /** @var int */
25
    // public $type;
26
    /** @var null|Node|NodeList|string */
27
    public $value;
28
29
    /** @var null|Node */
30
    private $parent;
31
32
    /**
33
     * Create the Node object and parses $nodeString IF not null (else assume a root type Node)
34
     *
35
     * @param string|null $nodeString The node string
36
     * @param int|null    $line       The line
37
     */
38
    public function __construct(string $nodeString = null, $line = 0)
39
    {
40
        $this->raw = $nodeString;
41
        $this->line = (int) $line;
42
         //permissive to tabs but replacement
43
        $nodeValue = preg_replace("/^\t+/m", " ", $nodeString);
44
        $this->indent = strspn($nodeValue, ' ');
45
        // $this->value = $nodeString;
46
    }
47
48
    /**
49
     * Sets the parent of the current Node
50
     *
51
     * @param Node $node The node
52
     *
53
     * @return Node|self The currentNode
54
     */
55
    public function setParent(Node $node):Node
56
    {
57
        $this->parent = $node;
58
        return $this;
59
    }
60
61
    /**
62
     * Gets the ancestor with specified $indent or the direct $parent OR the current Node itself
63
     *
64
     * @param int|null $indent The indent
65
     * @param int $type  first ancestor of this YAML::type is returned
66
     *
67
     * @return Node   The parent.
68
     */
69
    public function getParent(int $indent = null, $type = 0):Node
0 ignored issues
show
Unused Code introduced by
The parameter $type is not used and could be removed. ( Ignorable by Annotation )

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

69
    public function getParent(int $indent = null, /** @scrutinizer ignore-unused */ $type = 0):Node

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
70
    {
71
        if (!is_int($indent)) return $this->parent ?? $this;
72
        $cursor = $this;
73
        while ($cursor instanceof Node
74
               && $cursor->indent !== $indent
75
               && !($cursor->parent instanceof NodeRoot)) {
76
            $cursor = $cursor->parent;
77
        }
78
        return $cursor->parent ?? $this;
79
    }
80
81
    /**
82
     * Set the value for the current Node :
83
     * - if value is null , then value = $child (Node)
84
     * - if value is Node, then value is a NodeList with (previous value AND $child)
85
     * - if value is a NodeList, push $child into and set NodeList type accordingly
86
     *
87
     * @param Node $child The child
88
     *
89
     * @todo  refine the conditions when Y::LITTERALS
90
     */
91
    public function add(Node $child)
92
    {
93
        $child->setParent($this);
94
        if (is_null($this->value)) {
95
            $this->value = $child;
96
        } elseif ($this->value instanceof Node) {
97
            $this->value = new NodeList($this->value);
98
            $this->value->push($child);
99
        } elseif ($this->value instanceof NodeList) {
100
            $this->value->push($child);
101
        // } else {
102
        //     $this->value = new NodeScalar($this->raw, $this->line);
103
        }
104
    }
105
106
    /**
107
     * Gets the deepest node.
108
     *
109
     * @return Node|self  The deepest node.
110
     */
111
    public function getDeepestNode():Node
112
    {
113
        $cursor = $this;
114
        while ($cursor->value instanceof Node) {
115
            if ($cursor->value instanceof NodeList) {
116
                if ($cursor->value->count() > 0) {
117
                    $cursor = $cursor->value->OffsetGet(0);
118
                } else {
119
                    // $cursor = $cursor;
120
                    break;
121
                }
122
            } else {
123
                $cursor = $cursor->value;
124
            }
125
        }
126
        return $cursor;
127
    }
128
129
130
    /**
131
     * For certain (special) Nodes types some actions are required BEFORE parent assignment
132
     *
133
     * @param Node   $previous   The previous Node
134
     * @param array  $emptyLines The empty lines
135
     *
136
     * @return boolean  if True self::parse skips changing previous and adding to parent
137
     * @see self::parse
138
     */
139
    public function needsSpecialProcess(Node &$previous, array &$emptyLines):bool
140
    {
141
        $deepest = $previous->getDeepestNode();
142
        if ($deepest instanceof NodePartial) {
143
            return $deepest->specialProcess($this, $emptyLines);
144
        } else {
145
            return $this->specialProcess($previous, $emptyLines);
146
        }
147
    }
148
149
    public function specialProcess(Node &$previous, array &$emptyLines)
150
    {
151
        // $deepest = $previous->getDeepestNode();
152
        // //what first character to determine if escaped sequence are allowed
153
        // $val = ($deepest->value[-1] !== "\n" ? ' ' : '').substr($this->raw, $this->indent);
154
        // $deepest->parse($deepest->value.$val);
155
        return false;
156
    }
157
158
159
    public function getTargetOnLessIndent(Node $previous):Node
160
    {
161
        return $previous->getParent($this->indent);
162
    }
163
164
    /**
165
     * Modify parent target when current Node indentation is equal to previous node indentation
166
     *
167
     * @param Node $previous The previous
168
     *
169
     * @return Node
170
     */
171
    public function getTargetonEqualIndent(Node &$previous):Node
172
    {
173
        return $previous->getParent();
174
    }
175
176
   /**
177
     * Modify parent target when current Node indentation is superior to previous node indentation
178
     *
179
     * @param Node $previous The previous
180
     *
181
     * @return Node
182
     */
183
    public function getTargetOnMoreIndent(Node &$previous):Node
184
    {
185
        $target = $previous;
186
        $deepest = $previous->getDeepestNode();
187
        if ($deepest->isAwaitingChildren()) {
188
            $target = $deepest;
189
        }
190
        return $target;
191
    }
192
193
    public function isAwaitingChildren()
194
    {
195
        return false;
196
    }
197
198
   /**
199
     * According to the current Node type and deepest value
200
     * this indicates if self::parse skips (or not) the parent and previous assignment
201
     *
202
     * @param  Node     $target    The current Node as target parent
203
     *
204
     * @return boolean  True if context, False otherwiser
205
     * @todo   is this really necessary according ot other checkings out there ?
206
     */
207
    public function skipOnContext(Node &$target):bool
0 ignored issues
show
Unused Code introduced by
The parameter $target is not used and could be removed. ( Ignorable by Annotation )

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

207
    public function skipOnContext(/** @scrutinizer ignore-unused */ Node &$target):bool

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
208
    {
209
        return false;
210
    }
211
212
213
    public function getValue(&$parent = null)
214
    {
215
        if (is_null($this->value))                return null;
216
        elseif (is_string($this->value))          return $this->getScalar($this->value);
217
        elseif ($this->value instanceof Node)     return $this->value->getValue($parent);
218
        elseif ($this->value instanceof NodeList) return Builder::buildNodeList($this->value, $parent);
0 ignored issues
show
introduced by
$this->value is always a sub-type of Dallgoot\Yaml\NodeList.
Loading history...
219
        else {
220
            throw new \ParseError("Error trying to getValue of ".gettype($this->value));
221
        }
222
    }
223
224
    /**
225
     * Returns the correct PHP type according to the string value
226
     *
227
     * @param string $v a string value
228
     *
229
     * @return mixed The value with appropriate PHP type
230
     * @throws \Exception if happens in Regex::isDate or Regex::isNumber
231
     */
232
    public static function getScalar(string $v)
233
    {
234
        if (Regex::isDate($v))   return self::$dateAsString ? $v : date_create($v);
235
        if (Regex::isNumber($v)) return self::getNumber($v);
236
        $types = ['yes'   => true,
237
                    'no'    => false,
238
                    'true'  => true,
239
                    'false' => false,
240
                    'null'  => null,
241
                    '.inf'  => INF,
242
                    '-.inf' => -INF,
243
                    '.nan'  => NAN
244
        ];
245
        return array_key_exists(strtolower($v), $types) ? $types[strtolower($v)] : $v;
246
    }
247
248
    /**
249
     * Returns the correct PHP type according to the string value
250
     *
251
     * @param string $v a string value
252
     *
253
     * @return int|float   The scalar value with appropriate PHP type
254
     * @todo make sure there 's only ONE dot before cosndering a float
255
     */
256
    private static function getNumber(string $v)
257
    {
258
        if (preg_match(Regex::OCTAL_NUM, $v)) return intval(base_convert($v, 8, 10));
259
        if (preg_match(Regex::HEX_NUM, $v))   return intval(base_convert($v, 16, 10));
260
        return is_bool(strpos($v, '.')) ? intval($v) : floatval($v);
0 ignored issues
show
introduced by
The condition is_bool(strpos($v, '.')) is always false.
Loading history...
261
    }
262
263
    /**
264
     * Generic function to distinguish between Node and NodeList
265
     *
266
     * @param mixed         $parent The parent
267
     *
268
     * @return mixed  ( description_of_the_return_value )
269
     */
270
    public function build(&$parent = null)
271
    {
272
        // if ($this->value instanceof NodeList) return Builder::buildNodeList($this->value, $parent);
273
        // if ($this->value instanceof Node) return $this->build($parent);
274
        // return self::build($parent);
275
        return $this->getValue($this->value);
276
    }
277
278
279
    /**
280
     * PHP internal function for debugging purpose : simplify output provided by 'var_dump'
281
     *
282
     * @return array  the Node properties and respective values displayed by 'var_dump'
283
     */
284
    public function __debugInfo():array
285
    {
286
        $props = ['line'  => $this->line,
287
                'indent'=> $this->indent,
288
                'value' => $this->value,
289
                'raw'   => $this->raw,
290
            ];
291
        if ($this->identifier) $props['identifier'] = "($this->identifier)";
292
        return $props;
293
    }
294
}
295