Passed
Push — master ( b6ba86...2e4ed0 )
by Thomas
02:28
created

XMLReader   B

Complexity

Total Complexity 37

Size/Duplication

Total Lines 161
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 161
ccs 63
cts 63
cp 1
rs 8.6
c 1
b 0
f 0
wmc 37
lcom 1
cbo 5

8 Methods

Rating   Name   Duplication   Size   Complexity  
A prepareCondition() 0 12 4
A __construct() 0 3 1
A registerNamespace() 0 3 1
C next() 0 23 13
C read() 0 27 12
A getAttribute() 0 7 2
A expand() 0 8 2
A attachStream() 0 8 2
1
<?php
2
3
namespace FluentDOM {
4
5
  use FluentDOM\DOM\Document;
6
  use FluentDOM\DOM\Node;
7
  use FluentDOM\Exceptions\InvalidArgument;
8
  use FluentDOM\Utility\Namespaces;
9
  use FluentDOM\Utility\QualifiedName;
10
  use FluentDOM\Utility\ResourceWrapper;
11
12
  class XMLReader extends \XMLReader {
13
14
    /**
15
     * @var Namespaces
16
     */
17
    private $_namespaces;
18
19
    /**
20
     * Store last used document to avoid early GC
21
     * @var Document
22
     */
23
    private $_document;
24
25 15
    public function __construct() {
26 15
      $this->_namespaces = new Namespaces();
27 15
    }
28
29
    /**
30
     * register a namespace prefix for the xml reader, it will be used in
31
     * next() and other methods with a tag name argument
32
     *
33
     * @param string $prefix
34
     * @param string $namespaceURI
35
     * @throws \LogicException
36
     */
37 4
    public function registerNamespace(string $prefix, string $namespaceURI) {
38 4
      $this->_namespaces[$prefix] = $namespaceURI;
39 4
    }
40
41
    /**
42
     * Positions cursor on the next node skipping all subtrees. If $name contains
43
     * a namespace prefix it will be resolved using the registered namespaces.
44
     *
45
     * @param NULL|string $name The name of the next node to move to.
46
     * @param NULL|string $namespaceURI
47
     * @param callable|NULL $filter
48
     * @return bool
49
     */
50 5
    public function next($name = NULL, string $namespaceURI = NULL, callable $filter = NULL): bool {
51 5
      if (NULL !== $name) {
52 3
        list($localName, $namespaceURI, $ignoreNamespace) = $this->prepareCondition($name, $namespaceURI);
53
      } else {
54 2
        $ignoreNamespace = NULL === $namespaceURI || '' === $namespaceURI;
55 2
        $localName = $name;
56 2
        $namespaceURI = '';
57
      }
58 5
      if ($ignoreNamespace && !$filter) {
59 2
        return NULL !== $name ? parent::next($name) : parent::next();
60
      }
61 3
      $found = empty($localName) ? parent::next() : parent::next($localName);
62 3
      while ($found) {
63
        if (
64 3
          ($ignoreNamespace || $this->namespaceURI === $namespaceURI) &&
65 3
          (!$filter || $filter($this))
66
        ) {
67 3
          return TRUE;
68
        }
69 3
        $found = empty($localName) ? parent::next() : parent::next($localName);
70
      }
71 3
      return FALSE;
72
    }
73
74
    /**
75
     * Move to next node in document, including subtrees. If $name contains
76
     * a namespace prefix it will be resolved using the registered namespaces.
77
     *
78
     * @param NULL|string $name The name of the next node to move to.
79
     * @param NULL|string $namespaceURI
80
     * @param callable|NULL $filter
81
     * @return bool
82
     */
83 14
    public function read(string $name = NULL, string $namespaceURI = NULL, callable $filter = NULL): bool {
84 14
      if (NULL !== $name) {
85 8
        list($localName, $namespaceURI, $ignoreNamespace) = $this->prepareCondition($name, $namespaceURI);
86 8
        while (parent::read()) {
87
          if (
88 8
            $this->nodeType === XML_ELEMENT_NODE &&
89 8
            $this->localName === $localName &&
90
            (
91 8
              ($ignoreNamespace || ($this->namespaceURI === $namespaceURI)) &&
92 8
              (!$filter || $filter($this))
93
            )
94
          ) {
95 8
            return TRUE;
96
          }
97
        }
98 4
        return FALSE;
99
      }
100 6
      if ($filter) {
101 2
        while (parent::read()) {
102 2
          if ($filter($this)) {
103 2
            return TRUE;
104
          }
105
        }
106 1
        return FALSE;
107
      }
108 4
      return parent::read();
109
    }
110
111
    /**
112
     * Return attribute by name, resolve namespace prefix if included.
113
     *
114
     * @param string $name
115
     * @return NULL|string
116
     */
117 11
    public function getAttribute($name) {
118 11
      list($prefix, $localName) = QualifiedName::split($name);
119 11
      if (empty($prefix)) {
120 10
        return parent::getAttribute($name);
121
      }
122 1
      return parent::getAttributeNs($localName, $this->_namespaces->resolveNamespace($prefix));
123
    }
124
125
    /**
126
     * @param \DOMNode $baseNode
127
     * @return Node|\DOMNode
128
     * @throws \LogicException
129
     */
130 2
    public function expand($baseNode = NULL) {
131 2
      if (NULL !== $baseNode) {
132 1
        return parent::expand($baseNode);
133
      }
134 1
      $this->_document = $document = new Document();
135 1
      $document->namespaces($this->_namespaces);
136 1
      return parent::expand($document);
137
    }
138
139
    /**
140
     * @param $name
141
     * @param $namespaceURI
142
     * @return array
143
     */
144 8
    private function prepareCondition($name, $namespaceURI): array {
145 8
      if (NULL !== $namespaceURI) {
146 2
        $localName = $name;
147 2
        $namespaceURI = (string)$namespaceURI;
148 2
        $ignoreNamespace = FALSE;
149
      } else {
150 6
        list($prefix, $localName) = QualifiedName::split($name);
151 6
        $namespaceURI = $prefix ? $this->_namespaces->resolveNamespace($prefix) : '';
152 6
        $ignoreNamespace = ($prefix === FALSE && $namespaceURI === '');
153
      }
154 8
      return [$localName, $namespaceURI, $ignoreNamespace];
155
    }
156
157
    /**
158
     * @param resource $stream
159
     * @param string $encoding
160
     * @param int $options
161
     * @return bool
162
     * @throws \FluentDOM\Exceptions\InvalidArgument
163
     */
164 2
    public function attachStream($stream, string $encoding = NULL, int $options = 0): bool {
165 2
      if (!is_resource($stream)) {
166 1
        throw new InvalidArgument('stream', 'resource');
167
      }
168 1
      list($uri, $context) = ResourceWrapper::createContext($stream);
169 1
      libxml_set_streams_context($context);
170 1
      return $this->open($uri, $encoding, $options);
171
    }
172
  }
173
}
174