Completed
Pull Request — master (#11)
by Eric
62:01
created

XmlSerializationVisitor::visitText()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
crap 1
1
<?php
2
3
/*
4
 * This file is part of the Ivory Serializer package.
5
 *
6
 * (c) Eric GELOEN <[email protected]>
7
 *
8
 * For the full copyright and license information, please read the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Ivory\Serializer\Visitor\Xml;
13
14
use Ivory\Serializer\Accessor\AccessorInterface;
15
use Ivory\Serializer\Context\ContextInterface;
16
use Ivory\Serializer\Mapping\ClassMetadataInterface;
17
use Ivory\Serializer\Mapping\PropertyMetadataInterface;
18
use Ivory\Serializer\Mapping\TypeMetadataInterface;
19
use Ivory\Serializer\Visitor\AbstractVisitor;
20
21
/**
22
 * @author GeLo <[email protected]>
23
 */
24
class XmlSerializationVisitor extends AbstractVisitor
25
{
26
    /**
27
     * @var AccessorInterface
28
     */
29
    private $accessor;
30
31
32
    /**
33
     * @var \DOMDocument|null
34
     */
35
    private $document;
36
37
    /**
38
     * @var \DOMElement|null
39
     */
40
    private $node;
41
42
    /**
43
     * @var \DOMElement[]
44
     */
45
    private $stack;
46
47
    /**
48
     * @var string
49
     */
50
    private $version;
51
52
    /**
53
     * @var string
54
     */
55
    private $encoding;
56
57
    /**
58
     * @var bool
59
     */
60
    private $formatOutput;
61
62
    /**
63
     * @var string
64
     */
65
    private $root;
66
67
    /**
68
     * @var string
69
     */
70
    private $entry;
71
72
    /**
73
     * @var string
74
     */
75
    private $entryAttribute;
76
77
    /**
78 984
     * @param AccessorInterface $accessor
79
     * @param string            $version
80
     * @param string            $encoding
81
     * @param bool              $formatOutput
82
     * @param string            $root
83
     * @param string            $entry
84
     * @param string            $entryAttribute
85
     */
86 984
    public function __construct(
87 984
        AccessorInterface $accessor,
88 984
        $version = '1.0',
89 984
        $encoding = 'UTF-8',
90 984
        $formatOutput = true,
91 984
        $root = 'result',
92 984
        $entry = 'entry',
93
        $entryAttribute = 'key'
94
    ) {
95
        $this->accessor = $accessor;
96
        $this->version = $version;
97 147
        $this->encoding = $encoding;
98
        $this->formatOutput = $formatOutput;
99 147
        $this->root = $root;
100 147
        $this->entry = $entry;
101 147
        $this->entryAttribute = $entryAttribute;
102
    }
103 147
104
    /**
105
     * {@inheritdoc}
106
     */
107
    public function prepare($data, ContextInterface $context)
108
    {
109 9
        $this->document = null;
110
        $this->node = null;
111 9
        $this->stack = [];
112
113
        return parent::prepare($data, $context);
114
    }
115
116
    /**
117 48
     * {@inheritdoc}
118
     */
119 48
    public function visitBoolean($data, TypeMetadataInterface $type, ContextInterface $context)
120
    {
121
        return $this->visitText($data ? 'true' : 'false');
122
    }
123
124
    /**
125 9
     * {@inheritdoc}
126
     */
127 9
    public function visitData($data, TypeMetadataInterface $type, ContextInterface $context)
128
    {
129 9
        return $this->visitText($data);
130 9
    }
131 6
132
    /**
133 9
     * {@inheritdoc}
134
     */
135 View Code Duplication
    public function visitFloat($data, TypeMetadataInterface $type, ContextInterface $context)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
136
    {
137
        $data = (string) $data;
138
139 93
        if (strpos($data, '.') === false) {
140
            $data .= '.0';
141 93
        }
142 93
143
        return $this->visitText($data);
144 93
    }
145 62
146 93
    /**
147
     * {@inheritdoc}
148 93
     */
149
    public function visitString($data, TypeMetadataInterface $type, ContextInterface $context)
150
    {
151
        $document = $this->getDocument();
152
        $data = (string) $data;
153
154 147
        $node = $this->requireCData($data)
155
            ? $document->createCDATASection($data)
156 147
            : $document->createTextNode($data);
157
158 147
        return $this->visitNode($node);
159 147
    }
160 98
161
    /**
162 147
     * {@inheritdoc}
163
     */
164
    public function startVisitingObject($data, ClassMetadataInterface $class, ContextInterface $context)
165
    {
166
        $result = parent::startVisitingObject($data, $class, $context);
167
168 120
        if ($result && $class->hasXmlRoot()) {
169
            $this->getDocument($class->getXmlRoot());
170
        }
171
172
        return $result;
173
    }
174 120
175 6
    /**
176
     * {@inheritdoc}
177
     */
178
    public function getResult()
179 120
    {
180 80
        $document = $this->getDocument();
181 120
182 80
        if ($document->formatOutput) {
183
            $document->loadXML($document->saveXML());
184 120
        }
185 6
186
        return $document->saveXML();
187
    }
188 117
189 117
    /**
190 117
     * {@inheritdoc}
191 117
     */
192 117
    protected function doVisitObjectProperty(
193
        $data,
194 117
        $name,
195
        PropertyMetadataInterface $property,
196
        ContextInterface $context
197
    ) {
198
        if (!$property->isReadable()) {
199
            return false;
200 27
        }
201
202 27
        $value = $this->accessor->getValue(
203 27
            $data,
204
            $property->hasAccessor() ? $property->getAccessor() : $property->getName()
205 27
        );
206
207 27
        if ($value === null && $context->isNullIgnored()) {
208 15
            return false;
209 3
        }
210
211
        $node = $this->createNode($name);
212 12
        $this->enterNodeScope($node);
213
        $this->navigator->navigate($value, $context, $property->getType());
214 12
        $this->leaveNodeScope();
215 12
216 8
        if ($property->isXmlAttribute()) {
217
            $this->node->setAttribute($name, $node->nodeValue);
218 12
        } elseif ($property->isXmlValue()) {
219 12
            $this->visitNode($node->firstChild);
220 12
        } else {
221 12
            $this->visitNode($node);
222 18
        }
223 27
224
        return true;
225
    }
226
227
    /**
228 54
     * {@inheritdoc}
229
     */
230 54
    protected function doVisitArray($data, TypeMetadataInterface $type, ContextInterface $context)
231
    {
232
        $ignoreNull = $context->isNullIgnored();
233
        $valueType = $type->getOption('value');
234
235
        $this->getDocument();
236
237
        foreach ($data as $key => $value) {
238 141
            if ($value === null && $ignoreNull) {
239
                continue;
240 141
            }
241 141
242 94
            $node = $this->createNode(is_string($key) ? $key : $this->entry);
243
244 141
            if (is_int($key)) {
245
                $node->setAttribute($this->entryAttribute, $key);
246
            }
247
248
            $this->enterNodeScope($node);
249
            $this->navigator->navigate($value, $context, $valueType);
250 120
            $this->leaveNodeScope();
251
            $this->visitNode($node);
252 120
        }
253 120
    }
254 120
255
    /**
256 120
     * {@inheritdoc}
257
     */
258 120
    private function visitText($data)
259 120
    {
260
        return $this->visitNode($this->getDocument()->createTextNode((string) $data));
261
    }
262
263
    /**
264
     * @param \DOMNode $node
265
     *
266 93
     * @return \DOMNode
267
     */
268 93
    private function visitNode(\DOMNode $node)
269
    {
270
        if ($this->node !== $node) {
271
            $this->node->appendChild($node);
272
        }
273
274 147
        return $node;
275
    }
276 147
277
    /**
278
     * @param \DOMElement $node
279
     */
280
    private function enterNodeScope(\DOMElement $node)
281
    {
282
        $this->stack[] = $this->node;
283
        $this->node = $node;
284 120
    }
285
286 120
    private function leaveNodeScope()
287
    {
288
        $this->node = array_pop($this->stack);
289 120
    }
290 81
291 3
    /**
292 3
     * @param string $data
293
     *
294
     * @return bool
295 120
     */
296
    private function requireCData($data)
297
    {
298
        return strpos($data, '<') !== false || strpos($data, '>') !== false || strpos($data, '&') !== false;
299
    }
300
301 147
    /**
302
     * @param string|null $root
303 147
     *
304 147
     * @return \DOMDocument
305
     */
306 147
    private function getDocument($root = null)
307 147
    {
308
        return $this->document !== null ? $this->document : $this->document = $this->createDocument($root);
309 147
    }
310
311
    /**
312
     * @param string $name
313
     *
314
     * @return \DOMElement
315
     */
316
    private function createNode($name)
317
    {
318
        $document = $this->getDocument();
319
320
        try {
321
            $element = $document->createElement($name);
322
        } catch (\DOMException $e) {
323
            $element = $document->createElement($this->entry);
324
            $element->setAttribute($this->entryAttribute, $name);
325
        }
326
327
        return $element;
328
    }
329
330
    /**
331
     * @param string|null $root
332
     *
333
     * @return \DOMDocument
334
     */
335
    private function createDocument($root = null)
336
    {
337
        $document = new \DOMDocument($this->version, $this->encoding);
338
        $document->formatOutput = $this->formatOutput;
339
340
        $this->node = $document->createElement($root ?: $this->root);
341
        $document->appendChild($this->node);
342
343
        return $document;
344
    }
345
}
346