Parser   A
last analyzed

Complexity

Total Complexity 17

Size/Duplication

Total Lines 95
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 66.67%

Importance

Changes 0
Metric Value
wmc 17
lcom 1
cbo 2
dl 0
loc 95
ccs 32
cts 48
cp 0.6667
rs 10
c 0
b 0
f 0

4 Methods

Rating   Name   Duplication   Size   Complexity  
A parse() 0 15 4
A __construct() 0 9 3
A parsePartial() 0 19 3
B parseFull() 0 30 7
1
<?php
2
/*
3
 * This file is part of the Ariadne Component Library.
4
 *
5
 * (c) Muze <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace arc\xml;
12
13
/**
14
 * This class implements a XML parser based on DOMDocument->loadXML()
15
 * But it returns a Proxy for both SimpleXMLElement and DOMElement.
16
 * It also allows parsing of partial XML content.
17
 */
18
class Parser
19
{
20
21
    /**
22
     * A list of namespaces to use when importing partial xml
23
     * @var string[] $namespaces
24
     */
25
    public $namespaces = array();
26
27
    /**
28
     * @param array $options Allows you to set the namespaces property immediately
29
     */
30 18
    public function __construct( $options = array() )
31
    {
32 18
        $optionList = array( 'namespaces' );
33 18
        foreach( $options as $option => $optionValue) {
34
            if (in_array( $option, $optionList )) {
35
                $this->{$option} = $optionValue;
36
            }
37
        }
38 18
    }
39
40
    /**
41
     * Parses an XML string and returns a Proxy for it.
42
     * @param string|Proxy|null $xml
43
     * @param string $encoding The character set to use, defaults to UTF-8
44
     * @return Proxy
45
     */
46 18
    public function parse( $xml=null, $encoding = null )
47
    {
48 18
        if (!$xml) {
49
            return Proxy( null );
50
        }
51 18
        if ($xml instanceof Proxy) { // already parsed
52
            return $xml->cloneNode();
0 ignored issues
show
Documentation Bug introduced by
The method cloneNode does not exist on object<arc\xml\Proxy>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
53
        }
54 18
        $xml = (string) $xml;
55
        try {
56 18
            return $this->parseFull( $xml, $encoding );
57 2
        } catch( \arc\UnknownError $e) {
58 2
            return $this->parsePartial( $xml, $encoding );
59
        }
60
    }
61
62 2
    private function parsePartial( $xml, $encoding )
63
    {
64
        // add a known (single) root element with all declared namespaces
65
        // libxml will barf on multiple root elements
66
        // and it will silently drop namespace prefixes not defined in the document
67 2
        $root = '<arcxmlroot';
68 2
        foreach ($this->namespaces as $name => $uri) {
69
            if ($name === 0) {
70
                $root .= ' xmlns="';
71
            } else {
72
                $root .= ' xmlns:'.$name.'="';
73
            }
74
            $root .= htmlspecialchars( $uri ) . '"';
75
        }
76 2
        $root  .= '>';
77 2
        $result = $this->parseFull( $root.$xml.'</arcxmlroot>', $encoding );
78
        $result = $result->firstChild->childNodes;
0 ignored issues
show
Documentation introduced by
The property firstChild does not exist on object<arc\xml\Proxy>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
79
        return $result;
80
    }
81
82 18
    private function parseFull( $xml, $encoding = null )
83
    {
84 18
        $dom = new \DomDocument();
85 18
        if ($encoding) {
86
            $xml = '<?xml encoding="' . $encoding . '">' . $xml;
87
        }
88 18
        libxml_disable_entity_loader(); // prevents XXE attacks
89 18
        $prevErrorSetting = libxml_use_internal_errors(true);
90 18
        if ($dom->loadXML( $xml )) {
91 18
            if ($encoding) {
92
                foreach( $dom->childNodes as $item) {
93
                    if ($item->nodeType == XML_PI_NODE) {
94
                        $dom->removeChild( $item );
95
                        break;
96
                    }
97
                }
98
                $dom->encoding = $encoding;
99
            }
100 18
	        libxml_use_internal_errors( $prevErrorSetting );
101 18
            return new Proxy( simplexml_import_dom( $dom ), $this );
102
        }
103 2
        $errors = libxml_get_errors();
104 2
        libxml_clear_errors();
105 2
        libxml_use_internal_errors( $prevErrorSetting );
106 2
        $message = 'Incorrect xml passed.';
107 2
        foreach ($errors as $error) {
108 2
            $message .= '\nline: '.$error->line.'; column: '.$error->column.'; '.$error->message;
109
        }
110 2
        throw new \arc\UnknownError( $message, \arc\exceptions::ILLEGAL_ARGUMENT );
111
    }
112
}
113