Xml   F
last analyzed

Complexity

Total Complexity 66

Size/Duplication

Total Lines 541
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 29

Test Coverage

Coverage 58.98%

Importance

Changes 0
Metric Value
dl 0
loc 541
ccs 151
cts 256
cp 0.5898
rs 3.12
c 0
b 0
f 0
wmc 66
lcom 1
cbo 29

17 Methods

Rating   Name   Duplication   Size   Complexity  
A transform() 0 25 2
A __construct() 0 18 1
A getTranslator() 0 4 1
A setTranslator() 0 4 1
A buildPartials() 0 11 2
F buildFile() 0 86 14
A createErrorEntry() 0 13 2
A getDestinationPath() 0 5 1
A buildFunction() 0 21 4
F buildClass() 0 87 21
A finalize() 0 31 1
A buildPackageTree() 0 16 3
A buildNamespaceTree() 0 16 3
A buildDeprecationList() 0 8 1
A getNodeListForTagBasedQuery() 0 15 1
A generateNamespaceTree() 0 24 5
A generateNamespaceElements() 0 13 3

How to fix   Complexity   

Complex Class

Complex classes like Xml often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Xml, and based on these observations, apply Extract Interface, too.

1
<?php
2
declare(strict_types=1);
3
4
/**
5
 * This file is part of phpDocumentor.
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 *
10
 * @author    Mike van Riel <[email protected]>
11
 * @copyright 2010-2018 Mike van Riel / Naenius (http://www.naenius.com)
12
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
13
 * @link      http://phpdoc.org
14
 */
15
16
namespace phpDocumentor\Plugin\Core\Transformer\Writer;
17
18
use phpDocumentor\Application;
19
use phpDocumentor\Descriptor\ClassDescriptor;
20
use phpDocumentor\Descriptor\ConstantDescriptor;
21
use phpDocumentor\Descriptor\FileDescriptor;
22
use phpDocumentor\Descriptor\FunctionDescriptor;
23
use phpDocumentor\Descriptor\InterfaceDescriptor;
24
use phpDocumentor\Descriptor\ProjectDescriptor;
25
use phpDocumentor\Descriptor\TraitDescriptor;
26
use phpDocumentor\Descriptor\Validator\Error;
27
use phpDocumentor\Plugin\Core\Transformer\Behaviour\Tag\AuthorTag;
28
use phpDocumentor\Plugin\Core\Transformer\Behaviour\Tag\CoversTag;
29
use phpDocumentor\Plugin\Core\Transformer\Behaviour\Tag\IgnoreTag;
30
use phpDocumentor\Plugin\Core\Transformer\Behaviour\Tag\InternalTag;
31
use phpDocumentor\Plugin\Core\Transformer\Behaviour\Tag\LicenseTag;
32
use phpDocumentor\Plugin\Core\Transformer\Behaviour\Tag\MethodTag;
33
use phpDocumentor\Plugin\Core\Transformer\Behaviour\Tag\ParamTag;
34
use phpDocumentor\Plugin\Core\Transformer\Behaviour\Tag\PropertyTag;
35
use phpDocumentor\Plugin\Core\Transformer\Behaviour\Tag\ReturnTag;
36
use phpDocumentor\Plugin\Core\Transformer\Behaviour\Tag\UsesTag;
37
use phpDocumentor\Plugin\Core\Transformer\Behaviour\Tag\VarTag;
38
use phpDocumentor\Plugin\Core\Transformer\Writer\Xml\ArgumentConverter;
39
use phpDocumentor\Plugin\Core\Transformer\Writer\Xml\ConstantConverter;
40
use phpDocumentor\Plugin\Core\Transformer\Writer\Xml\DocBlockConverter;
41
use phpDocumentor\Plugin\Core\Transformer\Writer\Xml\InterfaceConverter;
42
use phpDocumentor\Plugin\Core\Transformer\Writer\Xml\MethodConverter;
43
use phpDocumentor\Plugin\Core\Transformer\Writer\Xml\PropertyConverter;
44
use phpDocumentor\Plugin\Core\Transformer\Writer\Xml\TagConverter;
45
use phpDocumentor\Plugin\Core\Transformer\Writer\Xml\TraitConverter;
46
use phpDocumentor\Transformer\Router\RouterAbstract;
47
use phpDocumentor\Transformer\Transformation;
48
use phpDocumentor\Transformer\Transformer;
49
use phpDocumentor\Transformer\Writer\Translatable;
50
use phpDocumentor\Transformer\Writer\WriterAbstract;
51
use phpDocumentor\Translator\Translator;
52
53
/**
54
 * Converts the structural information of phpDocumentor into an XML file.
55
 */
56
class Xml extends WriterAbstract implements Translatable
57
{
58
    /** @var \DOMDocument $xml */
59
    protected $xml;
60
61
    /** @var Translator $translator */
62
    protected $translator;
63
64
    protected $docBlockConverter;
65
66
    protected $argumentConverter;
67
68
    protected $methodConverter;
69
70
    protected $propertyConverter;
71
72
    protected $constantConverter;
73
74
    protected $interfaceConverter;
75
76
    protected $traitConverter;
77
78 2
    public function __construct(RouterAbstract $router)
79
    {
80 2
        $this->docBlockConverter = new DocBlockConverter(new TagConverter(), $router);
81 2
        $this->argumentConverter = new ArgumentConverter();
82 2
        $this->methodConverter = new MethodConverter($this->argumentConverter, $this->docBlockConverter);
83 2
        $this->propertyConverter = new PropertyConverter($this->docBlockConverter);
84 2
        $this->constantConverter = new ConstantConverter($this->docBlockConverter);
85 2
        $this->interfaceConverter = new InterfaceConverter(
86 2
            $this->docBlockConverter,
87 2
            $this->methodConverter,
88 2
            $this->constantConverter
89
        );
90 2
        $this->traitConverter = new TraitConverter(
91 2
            $this->docBlockConverter,
92 2
            $this->methodConverter,
93 2
            $this->propertyConverter
94
        );
95 2
    }
96
97
    /**
98
     * Returns an instance of the object responsible for translating content.
99
     *
100
     * @return Translator
101
     */
102
    public function getTranslator()
103
    {
104
        return $this->translator;
105
    }
106
107
    /**
108
     * Sets a new object capable of translating strings on this writer.
109
     */
110 2
    public function setTranslator(Translator $translator)
111
    {
112 2
        $this->translator = $translator;
113 2
    }
114
115
    /**
116
     * This method generates the AST output
117
     *
118
     * @param ProjectDescriptor $project        Document containing the structure.
119
     * @param Transformation    $transformation Transformation to execute.
120
     */
121 2
    public function transform(ProjectDescriptor $project, Transformation $transformation)
122
    {
123 2
        $artifact = $this->getDestinationPath($transformation);
124
125 2
        $this->checkForSpacesInPath($artifact);
126
127 2
        $this->xml = new \DOMDocument('1.0', 'utf-8');
128 2
        $this->xml->formatOutput = true;
129 2
        $document_element = new \DOMElement('project');
130 2
        $this->xml->appendChild($document_element);
131
132 2
        $document_element->setAttribute('title', $project->getName());
133 2
        $document_element->setAttribute('version', Application::VERSION());
134
135 2
        $this->buildPartials($document_element, $project);
136
137 2
        $transformer = $transformation->getTransformer();
138
139 2
        foreach ($project->getFiles() as $file) {
140 1
            $this->buildFile($document_element, $file, $transformer);
141
        }
142
143 2
        $this->finalize($project);
144 2
        file_put_contents($artifact, $this->xml->saveXML());
145 2
    }
146
147 2
    protected function buildPartials(\DOMElement $parent, ProjectDescriptor $project)
148
    {
149 2
        $child = new \DOMElement('partials');
150 2
        $parent->appendChild($child);
151 2
        foreach ($project->getPartials() as $name => $element) {
152
            $partial = new \DOMElement('partial');
153
            $child->appendChild($partial);
154
            $partial->setAttribute('name', $name);
155
            $partial->appendChild(new \DOMText($element));
156
        }
157 2
    }
158
159 1
    protected function buildFile(\DOMElement $parent, FileDescriptor $file, Transformer $transformer)
160
    {
161 1
        $child = new \DOMElement('file');
162 1
        $parent->appendChild($child);
163
164 1
        $path = ltrim($file->getPath(), './');
165 1
        $child->setAttribute('path', $path);
166 1
        $child->setAttribute(
167 1
            'generated-path',
168 1
            $transformer->generateFilename($path)
169
        );
170 1
        $child->setAttribute('hash', $file->getHash());
171
172 1
        $this->docBlockConverter->convert($child, $file);
173
174
        // add namespace aliases
175 1
        foreach ($file->getNamespaceAliases() as $alias => $namespace) {
176 1
            $alias_obj = new \DOMElement('namespace-alias', (string) $namespace);
177 1
            $child->appendChild($alias_obj);
178 1
            $alias_obj->setAttribute('name', (string) $alias);
179
        }
180
181
        /** @var ConstantDescriptor $constant */
182 1
        foreach ($file->getConstants() as $constant) {
183
            $this->constantConverter->convert($child, $constant);
184
        }
185
186
        /** @var FunctionDescriptor $function */
187 1
        foreach ($file->getFunctions() as $function) {
188
            $this->buildFunction($child, $function);
189
        }
190
191
        /** @var InterfaceDescriptor $interface */
192 1
        foreach ($file->getInterfaces() as $interface) {
193
            $this->interfaceConverter->convert($child, $interface);
194
        }
195
196
        /** @var ClassDescriptor $class */
197 1
        foreach ($file->getClasses() as $class) {
198
            $this->buildClass($child, $class);
199
        }
200
201
        /** @var TraitDescriptor $class */
202 1
        foreach ($file->getTraits() as $trait) {
203
            $this->traitConverter->convert($child, $trait);
204
        }
205
206
        // add markers
207 1
        if (count($file->getMarkers()) > 0) {
208
            $markers = new \DOMElement('markers');
209
            $child->appendChild($markers);
210
211
            foreach ($file->getMarkers() as $marker) {
212
                if (! $marker['type']) {
213
                    continue;
214
                }
215
216
                $type = preg_replace('/[^A-Za-z0-9\-]/', '', $marker['type']);
217
                $marker_obj = new \DOMElement(strtolower($type));
218
                $markers->appendChild($marker_obj);
219
220
                if (array_key_exists('message', $marker)) {
221
                    $marker_obj->appendChild(new \DOMText(trim((string) $marker['message'])));
222
                }
223
                $marker_obj->setAttribute('line', (string) $marker['line']);
224
            }
225
        }
226
227 1
        $errors = $file->getAllErrors();
228 1
        if (count($errors) > 0) {
229
            $parse_errors = new \DOMElement('parse_markers');
230
            $child->appendChild($parse_errors);
231
232
            /** @var Error $error */
233
            foreach ($errors as $error) {
234
                $this->createErrorEntry($error, $parse_errors);
235
            }
236
        }
237
238
        // if we want to include the source for each file; append a new
239
        // element 'source' which contains a compressed, encoded version
240
        // of the source
241 1
        if ($file->getSource()) {
0 ignored issues
show
Bug Best Practice introduced by Mike van Riel
The expression $file->getSource() of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
242
            $child->appendChild(new \DOMElement('source', base64_encode(gzcompress($file->getSource()))));
243
        }
244 1
    }
245
246
    /**
247
     * Creates an entry in the ParseErrors collection of a file for a given error.
248
     *
249
     * @param Error       $error
250
     * @param \DOMElement $parse_errors
251
     */
252
    protected function createErrorEntry($error, $parse_errors)
253
    {
254
        $marker_obj = new \DOMElement(strtolower($error->getSeverity()));
255
        $parse_errors->appendChild($marker_obj);
256
257
        $message = ($this->getTranslator())
258
            ? vsprintf($this->getTranslator()->translate($error->getCode()), $error->getContext())
259
            : $error->getCode();
260
261
        $marker_obj->appendChild(new \DOMText($message));
262
        $marker_obj->setAttribute('line', $error->getLine());
263
        $marker_obj->setAttribute('code', $error->getCode());
264
    }
265
266
    /**
267
     * Retrieves the destination location for this artifact.
268
     *
269
     * @return string
270
     */
271 2
    protected function getDestinationPath(Transformation $transformation)
272
    {
273 2
        return $transformation->getTransformer()->getTarget()
274 2
            . DIRECTORY_SEPARATOR . $transformation->getArtifact();
275
    }
276
277
    /**
278
     * Export this function definition to the given parent DOMElement.
279
     *
280
     * @param \DOMElement        $parent   Element to augment.
281
     * @param FunctionDescriptor $function Element to export.
282
     * @param \DOMElement        $child    if supplied this element will be augmented instead of freshly added.
283
     */
284
    public function buildFunction(\DOMElement $parent, FunctionDescriptor $function, \DOMElement $child = null)
285
    {
286
        if (!$child) {
287
            $child = new \DOMElement('function');
288
            $parent->appendChild($child);
289
        }
290
291
        $namespace = $function->getNamespace()
292
            ?: $parent->getAttribute('namespace');
293
        $child->setAttribute('namespace', ltrim((string) $namespace, '\\'));
294
        $child->setAttribute('line', (string) $function->getLine());
295
296
        $child->appendChild(new \DOMElement('name', $function->getName()));
297
        $child->appendChild(new \DOMElement('full_name', (string) $function->getFullyQualifiedStructuralElementName()));
298
299
        $this->docBlockConverter->convert($child, $function);
300
301
        foreach ($function->getArguments() as $argument) {
302
            $this->argumentConverter->convert($child, $argument);
303
        }
304
    }
305
306
    /**
307
     * Exports the given reflection object to the parent XML element.
308
     *
309
     * This method creates a new child element on the given parent XML element
310
     * and takes the properties of the Reflection argument and sets the
311
     * elements and attributes on the child.
312
     *
313
     * If a child DOMElement is provided then the properties and attributes are
314
     * set on this but the child element is not appended onto the parent. This
315
     * is the responsibility of the invoker. Essentially this means that the
316
     * $parent argument is ignored in this case.
317
     *
318
     * @param \DOMElement     $parent The parent element to augment.
319
     * @param ClassDescriptor $class  The data source.
320
     * @param \DOMElement     $child  Optional: child element to use instead of creating a
321
     *      new one on the $parent.
322
     */
323
    public function buildClass(\DOMElement $parent, ClassDescriptor $class, \DOMElement $child = null)
324
    {
325
        if (!$child) {
326
            $child = new \DOMElement('class');
327
            $parent->appendChild($child);
328
        }
329
330
        $child->setAttribute('final', $class->isFinal() ? 'true' : 'false');
331
        $child->setAttribute('abstract', $class->isAbstract() ? 'true' : 'false');
332
333
        if ($class->getParent() !== null) {
334
            $parentFqcn = $class->getParent() instanceof ClassDescriptor
335
                ? (string) $class->getParent()->getFullyQualifiedStructuralElementName()
336
                : (string) $class->getParent();
337
            $child->appendChild(new \DOMElement('extends', $parentFqcn));
338
        }
339
340
        /** @var InterfaceDescriptor $interface */
341
        foreach ($class->getInterfaces() as $interface) {
342
            $interfaceFqcn = $interface instanceof InterfaceDescriptor
343
                ? (string) $interface->getFullyQualifiedStructuralElementName()
344
                : (string) $interface;
345
            $child->appendChild(new \DOMElement('implements', $interfaceFqcn));
346
        }
347
348
        if ($child === null) {
349
            $child = new \DOMElement('interface');
350
            $parent->appendChild($child);
351
        }
352
353
        $namespace = (string) $class->getNamespace()->getFullyQualifiedStructuralElementName();
354
        $child->setAttribute('namespace', ltrim($namespace, '\\'));
355
        $child->setAttribute('line', (string) $class->getLine());
356
357
        $child->appendChild(new \DOMElement('name', $class->getName()));
358
        $child->appendChild(new \DOMElement('full_name', (string) $class->getFullyQualifiedStructuralElementName()));
359
360
        $this->docBlockConverter->convert($child, $class);
361
362
        foreach ($class->getConstants() as $constant) {
363
            // TODO #840: Workaround; for some reason there are NULLs in the constants array.
364
            if ($constant) {
365
                $this->constantConverter->convert($child, $constant);
366
            }
367
        }
368
369
        foreach ($class->getInheritedConstants() as $constant) {
370
            // TODO #840: Workaround; for some reason there are NULLs in the constants array.
371
            if ($constant) {
372
                $this->constantConverter->convert($child, $constant);
373
            }
374
        }
375
376
        foreach ($class->getProperties() as $property) {
377
            // TODO #840: Workaround; for some reason there are NULLs in the properties array.
378
            if ($property) {
379
                $this->propertyConverter->convert($child, $property);
380
            }
381
        }
382
383
        foreach ($class->getInheritedProperties() as $property) {
384
            // TODO #840: Workaround; for some reason there are NULLs in the properties array.
385
            if ($property) {
386
                $this->propertyConverter->convert($child, $property);
387
            }
388
        }
389
390
        foreach ($class->getMethods() as $method) {
391
            // TODO #840: Workaround; for some reason there are NULLs in the methods array.
392
            if ($method) {
393
                $this->methodConverter->convert($child, $method);
394
            }
395
        }
396
397
        foreach ($class->getInheritedMethods() as $method) {
398
            // TODO #840: Workaround; for some reason there are NULLs in the methods array.
399
            if ($method) {
400
                $methodElement = $this->methodConverter->convert($child, $method);
401
                $methodElement->appendChild(
402
                    new \DOMElement(
403
                        'inherited_from',
404
                        (string) $method->getParent()->getFullyQualifiedStructuralElementName()
405
                    )
406
                );
407
            }
408
        }
409
    }
410
411
    /**
412
     * Finalizes the processing and executing all post-processing actions.
413
     *
414
     * This method is responsible for extracting and manipulating the data that
415
     * is global to the project, such as:
416
     *
417
     * - Package tree
418
     * - Namespace tree
419
     * - Marker list
420
     * - Deprecated elements listing
421
     * - Removal of objects related to visibility
422
     */
423 2
    protected function finalize(ProjectDescriptor $projectDescriptor)
424
    {
425
        // TODO: move all these behaviours to a central location for all template parsers
426 2
        $behaviour = new AuthorTag();
427 2
        $behaviour->process($this->xml);
428 2
        $behaviour = new CoversTag();
429 2
        $behaviour->process($this->xml);
430 2
        $behaviour = new IgnoreTag();
431 2
        $behaviour->process($this->xml);
432 2
        $behaviour = new InternalTag(
433 2
            $projectDescriptor->isVisibilityAllowed(ProjectDescriptor\Settings::VISIBILITY_INTERNAL)
434
        );
435 2
        $behaviour->process($this->xml);
436 2
        $behaviour = new LicenseTag();
437 2
        $behaviour->process($this->xml);
438 2
        $behaviour = new MethodTag();
439 2
        $behaviour->process($this->xml);
440 2
        $behaviour = new ParamTag();
441 2
        $behaviour->process($this->xml);
442 2
        $behaviour = new PropertyTag();
443 2
        $behaviour->process($this->xml);
444 2
        $behaviour = new ReturnTag();
445 2
        $behaviour->process($this->xml);
446 2
        $behaviour = new UsesTag();
447 2
        $behaviour->process($this->xml);
448 2
        $behaviour = new VarTag();
449 2
        $behaviour->process($this->xml);
450 2
        $this->buildPackageTree($this->xml);
451 2
        $this->buildNamespaceTree($this->xml);
452 2
        $this->buildDeprecationList($this->xml);
453 2
    }
454
455
    /**
456
     * Collects all packages and subpackages, and adds a new section in the
457
     * DOM to provide an overview.
458
     *
459
     * @param \DOMDocument $dom Packages are extracted and a summary inserted
460
     *     in this object.
461
     */
462 2
    protected function buildPackageTree(\DOMDocument $dom)
463
    {
464 2
        $xpath = new \DOMXPath($dom);
465 2
        $packages = ['global' => true];
466 2
        $qry = $xpath->query('//@package');
467 2
        for ($i = 0; $i < $qry->length; ++$i) {
468 1
            if (isset($packages[$qry->item($i)->nodeValue])) {
469
                continue;
470
            }
471
472 1
            $packages[$qry->item($i)->nodeValue] = true;
473
        }
474
475 2
        $packages = $this->generateNamespaceTree(array_keys($packages));
476 2
        $this->generateNamespaceElements($packages, $dom->documentElement, 'package');
477 2
    }
478
479
    /**
480
     * Collects all namespaces and sub-namespaces, and adds a new section in
481
     * the DOM to provide an overview.
482
     *
483
     * @param \DOMDocument $dom Namespaces are extracted and a summary inserted
484
     *     in this object.
485
     */
486 2
    protected function buildNamespaceTree(\DOMDocument $dom)
487
    {
488 2
        $xpath = new \DOMXPath($dom);
489 2
        $namespaces = [];
490 2
        $qry = $xpath->query('//@namespace');
491 2
        for ($i = 0; $i < $qry->length; ++$i) {
492
            if (isset($namespaces[$qry->item($i)->nodeValue])) {
493
                continue;
494
            }
495
496
            $namespaces[$qry->item($i)->nodeValue] = true;
497
        }
498
499 2
        $namespaces = $this->generateNamespaceTree(array_keys($namespaces));
500 2
        $this->generateNamespaceElements($namespaces, $dom->documentElement);
501 2
    }
502
503
    /**
504
     * Adds a node to the xml for deprecations and the count value
505
     *
506
     * @param \DOMDocument $dom Markers are extracted and a summary inserted in this object.
507
     */
508 2
    protected function buildDeprecationList(\DOMDocument $dom)
509
    {
510 2
        $nodes = $this->getNodeListForTagBasedQuery($dom, 'deprecated');
511
512 2
        $node = new \DOMElement('deprecated');
513 2
        $dom->documentElement->appendChild($node);
514 2
        $node->setAttribute('count', (string) $nodes->length);
515 2
    }
516
517
    /**
518
     * Build a tag based query string and return result
519
     *
520
     * @param \DOMDocument $dom    Markers are extracted and a summary inserted
521
     *      in this object.
522
     * @param string       $marker The marker we're searching for throughout xml
523
     *
524
     * @return \DOMNodeList
525
     */
526 2
    protected function getNodeListForTagBasedQuery($dom, $marker)
527
    {
528 2
        $xpath = new \DOMXPath($dom);
529
530 2
        $query = '/project/file/markers/' . $marker . '|';
531 2
        $query .= '/project/file/docblock/tag[@name="' . $marker . '"]|';
532 2
        $query .= '/project/file/class/docblock/tag[@name="' . $marker . '"]|';
533 2
        $query .= '/project/file/class/*/docblock/tag[@name="' . $marker . '"]|';
534 2
        $query .= '/project/file/interface/docblock/tag[@name="' . $marker . '"]|';
535 2
        $query .= '/project/file/interface/*/docblock/tag[@name="' . $marker . '"]|';
536 2
        $query .= '/project/file/function/docblock/tag[@name="' . $marker . '"]|';
537 2
        $query .= '/project/file/constant/docblock/tag[@name="' . $marker . '"]';
538
539 2
        return $xpath->query($query);
540
    }
541
542
    /**
543
     * Generates a hierarchical array of namespaces with their singular name
544
     * from a single level list of namespaces with their full name.
545
     *
546
     * @param array $namespaces the list of namespaces as retrieved from the xml.
547
     *
548
     * @return array
549
     */
550 2
    protected function generateNamespaceTree($namespaces)
551
    {
552 2
        sort($namespaces);
553
554 2
        $result = [];
555 2
        foreach ($namespaces as $namespace) {
556 2
            if (!$namespace) {
557
                $namespace = 'global';
558
            }
559
560 2
            $namespace_list = explode('\\', $namespace);
561
562 2
            $node = &$result;
563 2
            foreach ($namespace_list as $singular) {
564 2
                if (!isset($node[$singular])) {
565 2
                    $node[$singular] = [];
566
                }
567
568 2
                $node = &$node[$singular];
569
            }
570
        }
571
572 2
        return $result;
573
    }
574
575
    /**
576
     * Recursive method to create a hierarchical set of nodes in the dom.
577
     *
578
     * @param array[]     $namespaces     the list of namespaces to process.
579
     * @param \DOMElement $parent_element the node to receive the children of
580
     *                                    the above list.
581
     * @param string      $node_name      the name of the summary element.
582
     */
583 2
    protected function generateNamespaceElements($namespaces, $parent_element, $node_name = 'namespace')
584
    {
585 2
        foreach ($namespaces as $name => $sub_namespaces) {
586 2
            $node = new \DOMElement($node_name);
587 2
            $parent_element->appendChild($node);
588 2
            $node->setAttribute('name', $name);
589 2
            $fullName = $parent_element->nodeName === $node_name
590
                ? $parent_element->getAttribute('full_name') . '\\' . $name
591 2
                : $name;
592 2
            $node->setAttribute('full_name', $fullName);
593 2
            $this->generateNamespaceElements($sub_namespaces, $node, $node_name);
594
        }
595 2
    }
596
}
597