Completed
Push — master ( a5d47a...489c7c )
by Colin
25:01 queued 01:34
created

Element   D

Complexity

Total Complexity 58

Size/Duplication

Total Lines 274
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Test Coverage

Coverage 94.57%

Importance

Changes 0
Metric Value
wmc 58
lcom 1
cbo 0
dl 0
loc 274
ccs 87
cts 92
cp 0.9457
rs 4.5599
c 0
b 0
f 0

20 Methods

Rating   Name   Duplication   Size   Complexity  
A getSiblingPosition() 0 19 4
A __construct() 0 5 1
C isBlock() 0 23 16
A isText() 0 4 1
A isWhitespace() 0 4 2
A getTagName() 0 4 1
A getValue() 0 4 1
A getParent() 0 4 2
A getNextSibling() 0 4 2
A getPreviousSibling() 0 4 2
A hasChildren() 0 4 1
A getChildren() 0 10 2
A getNext() 0 11 3
A getNextNode() 0 14 5
A isDescendantOf() 0 18 5
A setFinalMarkdown() 0 5 1
A getChildrenAsString() 0 4 1
A getListItemLevel() 0 14 4
A getAttribute() 0 8 2
A equals() 0 8 2

How to fix   Complexity   

Complex Class

Complex classes like Element 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 Element, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace League\HTMLToMarkdown;
4
5
class Element implements ElementInterface
6
{
7
    /**
8
     * @var \DOMNode
9
     */
10
    protected $node;
11
12
    /**
13
     * @var ElementInterface|null
14
     */
15
    private $nextCached;
16
17 90
    /**
18
     * @var DOMNode|null
19 90
     */
20 90
    private $previousSiblingCached;
21
22
    public function __construct(\DOMNode $node)
23
    {
24
        $this->node = $node;
25 15
        $this->previousSiblingCached = $this->node->previousSibling;
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->node->previousSibling of type object<DOMNode> is incompatible with the declared type object<League\HTMLToMarkdown\DOMNode>|null of property $previousSiblingCached.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
26
    }
27 15
28 15
    /**
29 15
     * @return bool
30 15
     */
31 15
    public function isBlock()
32 15
    {
33 15
        switch ($this->getTagName()) {
34 15
            case 'blockquote':
35 15
            case 'body':
36 15
            case 'div':
37 15
            case 'h1':
38 15
            case 'h2':
39 15
            case 'h3':
40 12
            case 'h4':
41 12
            case 'h5':
42 12
            case 'h6':
43 3
            case 'hr':
44
            case 'html':
45 12
            case 'li':
46
            case 'p':
47
            case 'ol':
48
            case 'ul':
49
                return true;
50
            default:
51
                return false;
52 3
        }
53
    }
54 3
55
    /**
56
     * @return bool
57
     */
58
    public function isText()
59
    {
60 6
        return $this->getTagName() === '#text';
61
    }
62 6
63
    /**
64
     * @return bool
65
     */
66
    public function isWhitespace()
67
    {
68 90
        return $this->getTagName() === '#text' && trim($this->getValue()) === '';
69
    }
70 90
71
    /**
72
     * @return string
73
     */
74
    public function getTagName()
75
    {
76 78
        return $this->node->nodeName;
77
    }
78 78
79
    /**
80
     * @return string
81
     */
82
    public function getValue()
83
    {
84 75
        return $this->node->nodeValue;
85
    }
86 75
87
    /**
88
     * @return ElementInterface|null
89
     */
90
    public function getParent()
91
    {
92 90
        return new static($this->node->parentNode) ?: null;
93
    }
94 90
95
    /**
96
     * @return ElementInterface|null
97
     */
98
    public function getNextSibling()
99
    {
100 90
        return $this->node->nextSibling !== null ? new static($this->node->nextSibling) : null;
101
    }
102 90
103
    /**
104 90
     * @return ElementInterface|null
105 90
     */
106
    public function getPreviousSibling()
107
    {
108 90
        return $this->previousSiblingCached !== null ? new static($this->previousSiblingCached) : null;
109
    }
110
111
    /**
112
     * @return bool
113
     */
114 21
    public function hasChildren()
115
    {
116 21
        return $this->node->hasChildNodes();
117 21
    }
118 21
119 21
    /**
120
     * @return ElementInterface[]
121
     */
122
    public function getChildren()
123 21
    {
124
        $ret = array();
125
        /** @var \DOMNode $node */
126
        foreach ($this->node->childNodes as $node) {
127
            $ret[] = new static($node);
128
        }
129
130
        return $ret;
131
    }
132 21
133
    /**
134 21
     * @return ElementInterface|null
135
     */
136
    public function getNext()
137
    {
138 21
        if ($this->nextCached === null) {
139 21
            $nextNode = $this->getNextNode($this->node);
140
            if ($nextNode !== null) {
141
                $this->nextCached = new static($nextNode);
142 6
            }
143 6
        }
144
145
        return $this->nextCached;
146
    }
147
148
    /**
149
     * @param \DomNode $node
150
     * @param bool $checkChildren
151
     *
152 90
     * @return \DomNode|null
153
     */
154 90
    private function getNextNode($node, $checkChildren = true)
155 3
    {
156
        if ($checkChildren && $node->firstChild) {
157
            return $node->firstChild;
158 90
        }
159 90
160 90
        if ($node->nextSibling) {
161
            return $node->nextSibling;
162
        }
163 90
164 24
        if ($node->parentNode) {
165
            return $this->getNextNode($node->parentNode, false);
166
        }
167
    }
168
169
    /**
170
     * @param string[]|string $tagNames
171
     *
172
     * @return bool
173
     */
174 90
    public function isDescendantOf($tagNames)
175
    {
176 90
        if (!is_array($tagNames)) {
177 90
            $tagNames = array($tagNames);
178 90
        }
179
180
        for ($p = $this->node->parentNode; $p !== false; $p = $p->parentNode) {
181
            if (is_null($p)) {
182
                return false;
183 81
            }
184
185 81
            if (in_array($p->nodeName, $tagNames)) {
186
                return true;
187
            }
188
        }
189
190
        return false;
191 6
    }
192
193 6
    /**
194
     * @param string $markdown
195
     */
196 6
    public function setFinalMarkdown($markdown)
197 6
    {
198 6
        $markdown_node = $this->node->ownerDocument->createTextNode($markdown);
199
        $this->node->parentNode->replaceChild($markdown_node, $this->node);
200
    }
201
202
    /**
203 6
     * @return string
204 6
     */
205
    public function getChildrenAsString()
206
    {
207
        return $this->node->C14N();
208 6
    }
209
210
    /**
211
     * @return int
212
     */
213
    public function getSiblingPosition()
214 9
    {
215
        $position = 0;
216 9
217 9
        // Loop through all nodes and find the given $node
218
        foreach ($this->getParent()->getChildren() as $current_node) {
219 9
            if (!$current_node->isWhitespace()) {
220 9
                $position++;
221 3
            }
222
223 9
            // TODO: Need a less-buggy way of comparing these
224
            // Perhaps we can somehow ensure that we always have the exact same object and use === instead?
225
            if ($this->equals($current_node)) {
226 9
                break;
227
            }
228
        }
229
230
        return $position;
231
    }
232
233
    /**
234 30
     * @return int
235
     */
236 30
    public function getListItemLevel()
237 30
    {
238
        $level = 0;
239
        $parent = $this->getParent();
240
241
        while ($parent !== null && $parent->node->parentNode) {
242
            if ($parent->getTagName() === 'li') {
243
                $level++;
244
            }
245
            $parent = $parent->getParent();
246
        }
247
248 6
        return $level;
249
    }
250 6
251 6
    /**
252
     * @param string $name
253
     *
254
     * @return string
255
     */
256
    public function getAttribute($name)
257
    {
258
        if ($this->node instanceof \DOMElement) {
259
            return $this->node->getAttribute($name);
260
        }
261
262
        return '';
263
    }
264
265
    /**
266
     * @param ElementInterface $element
267
     *
268
     * @return bool
269
     */
270
    public function equals(ElementInterface $element)
271
    {
272
        if ($element instanceof self) {
273
            return $element->node === $this->node;
274
        }
275
276
        return $element === $this;
277
    }
278
}
279