Passed
Push — master ( 5988e7...23b4c3 )
by stéphane
02:08
created

Node::onSetElement()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 5
nc 4
nop 1
dl 0
loc 7
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Dallgoot\Yaml;
4
5
use Dallgoot\Yaml\{Yaml as Y, Regex as R};
6
7
/**
8
 *
9
 * @author  Stéphane Rebai <[email protected]>
10
 * @license Apache 2.0
11
 * @link    TODO : url to specific online doc
12
 */
13
final class Node
14
{
15
    /** @var int */
16
    public $indent = -1;
17
    /** @var int */
18
    public $line;
19
    /** @var int */
20
    public $type;
21
    /** @var null|string|boolean */
22
    public $identifier;
23
    /** @var Node|NodeList|null|string */
24
    public $value = null;
25
    /** @var null|string */
26
    public $raw;
27
28
    /** @var null|Node */
29
    private $parent;
30
31
    /**
32
     * Create the Node object and parses $nodeString IF not null (else assume a root type Node)
33
     *
34
     * @param string|null $nodeString The node string
35
     * @param int|null    $line       The line
36
     */
37
    public function __construct($nodeString = null, $line = 0)
38
    {
39
        $this->line = (int) $line;
40
        if (is_null($nodeString)) {
41
            $this->type = Y::ROOT;
42
        } else {
43
            $this->raw = $nodeString;
44
            $this->parse($nodeString);
45
        }
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|null   The parent.
68
     */
69
    public function getParent(int $indent = null, $type = 0):Node
70
    {
71
        if ($this->type === Y::ROOT) return $this;
72
        if (!is_int($indent)) return $this->parent ?? $this;
73
        $cursor = $this;
74
        while ($cursor instanceof Node && $cursor->indent >= $indent) {
75
            if ($cursor->indent === $indent && $cursor->type !== $type) {
76
                $cursor = $cursor->parent ?? $cursor;
77
                break;
78
            }
79
            $cursor = $cursor->parent;
80
        }
81
        return $cursor ?? $this;
82
    }
83
84
    /**
85
     * Set the value for the current Node :
86
     * - if value is null , then value = $child (Node)
87
     * - if value is Node, then value is a NodeList with (previous value AND $child)
88
     * - if value is a NodeList, push $child into and set NodeList type accordingly
89
     *
90
     * @param Node $child The child
91
     * @todo  refine the conditions when Y::LITTERALS
92
     */
93
    public function add(Node $child)
94
    {
95
        if ($this->type & (Y::SCALAR|Y::QUOTED)) {
96
            $this->getParent()->add($child);
97
            return;
98
        }
99
        $child->setParent($this);
100
        if (is_null($this->value)) {
101
            $this->value = $child;
102
            return;
103
        }elseif ($this->value instanceof Node) {
104
            if ($this->value->type & Y::LITTERALS) {
105
                $type = $this->value->type;
106
                $this->value = new NodeList();
107
                $this->value->type = $type;
108
            } else {
109
                $this->value = new NodeList($this->value);
110
            }
111
        }
112
        $this->value->push($child);
113
    }
114
115
    /**
116
     * Gets the deepest node.
117
     *
118
     * @return Node|self  The deepest node.
119
     */
120
    public function getDeepestNode():Node
121
    {
122
        $cursor = $this;
123
        while ($cursor->value instanceof Node || $cursor->value instanceof NodeList) {
124
            if ($cursor->value instanceof NodeList) {
125
                if ($cursor->value->count() === 1) {
126
                    $cursor = $cursor->value->OffsetGet(0);
127
                } else {
128
                    $cursor = $cursor;
129
                    break;
130
                }
131
            } else {
132
                $cursor = $cursor->value;
133
            }
134
        }
135
        return $cursor;
136
    }
137
138
    /**
139
     * Parses the string (assumed to be a line from a valid YAML)
140
     *
141
     * @param string $nodeString The node string
142
     *
143
     * @return Node|self
144
     */
145
    public function parse(string $nodeString):Node
146
    {
147
        $nodeValue = preg_replace("/^\t+/m", " ", $nodeString); //permissive to tabs but replacement
148
        $this->indent = strspn($nodeValue, ' ');
149
        $nodeValue = ltrim($nodeValue);
150
        if ($nodeValue === '') {
151
            $this->type = Y::BLANK;
152
        } elseif (substr($nodeValue, 0, 3) === '...') {//TODO: can have something on same line ?
153
            $this->type = Y::DOC_END;
154
        } elseif (preg_match(R::KEY, $nodeValue, $matches)) {
155
            NodeHandlers::onKey($matches, $this);
156
        } else {
157
            $this->identify($nodeValue);
158
        }
159
        return $this;
160
    }
161
162
    /**
163
     *  Set the type and value according to first character
164
     *
165
     * @param string $nodeValue The node value
166
     */
167
    private function identify($nodeValue)
168
    {
169
        $v = ltrim(substr($nodeValue, 1));
170
        $first = $nodeValue[0];
171
        if ($first === "-")                        NodeHandlers::onHyphen($nodeValue, $this);
172
        elseif (in_array($first, ['"', "'"]))      NodeHandlers::onQuoted($nodeValue, $this);
173
        elseif (in_array($first, ['{', '[']))      NodeHandlers::onCompact($nodeValue, $this);
174
        elseif (in_array($first, ['?', ':']))      NodeHandlers::onSetElement($nodeValue, $this);
175
        elseif (in_array($first, ['!', '&', '*'])) NodeHandlers::onNodeAction($nodeValue, $this);
176
        else {
177
            $characters = [ '#' =>  [Y::COMMENT, $v],
178
                            '%' =>  [Y::DIRECTIVE, $v],
179
                            '>' =>  [Y::LITT_FOLDED, null],
180
                            '|' =>  [Y::LITT, null]
181
                            ];
182
            if (isset($characters[$first])) {
183
                $this->type  = $characters[$first][0];
184
                $this->value = $characters[$first][1];
185
            } else {
186
                $this->type  = Y::SCALAR;
187
                $this->value = $nodeValue;
188
            }
189
        }
190
    }
191
192
    /**
193
     * PHP internal function for debugging purpose : simplify output provided by 'var_dump'
194
     *
195
     * @return array  the Node properties and respective values displayed by 'var_dump'
196
     */
197
    public function __debugInfo():array
198
    {
199
        return ['line'  => $this->line,
200
                'indent'=> $this->indent,
201
                'type'  => Y::getName($this->type).($this->identifier ? "($this->identifier)" : ''),
202
                'value' => $this->value];
203
    }
204
}
205