Scrutinizer GitHub App not installed

We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.

Install GitHub App

GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( a16ac0...33e1e8 )
by Der Mundschenk
15s
created

DOM   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 334
Duplicated Lines 9.88 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
wmc 50
lcom 1
cbo 1
dl 33
loc 334
rs 8.6206
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A block_tags() 0 12 3
A nodelist_to_array() 0 9 2
A get_ancestors() 0 9 3
C has_class() 0 27 8
A get_prev_chr() 0 3 1
A get_next_chr() 0 3 1
A get_adjacent_chr() 0 14 4
A get_previous_textnode() 0 6 1
A get_next_textnode() 0 6 1
C get_adjacent_textnode() 5 27 7
A get_first_textnode() 10 10 3
A get_last_textnode() 10 10 3
C get_edge_textnode() 8 22 7
A get_block_parent() 0 17 4
A get_block_parent_name() 0 9 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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

1
<?php
2
/**
3
 *  This file is part of PHP-Typography.
4
 *
5
 *  Copyright 2014-2017 Peter Putzer.
6
 *  Copyright 2009-2011 KINGdesk, LLC.
7
 *
8
 *  This program is free software; you can redistribute it and/or
9
 *  modify it under the terms of the GNU General Public License
10
 *  as published by the Free Software Foundation; either version 2
11
 *  of the License, or (at your option) any later version.
12
 *
13
 *  This program is distributed in the hope that it will be useful,
14
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 *  GNU General Public License for more details.
17
 *
18
 *  You should have received a copy of the GNU General Public License
19
 *  along with this program; if not, write to the Free Software
20
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21
 *
22
 *  ***
23
 *
24
 *  @package mundschenk-at/php-typography
25
 *  @license http://www.gnu.org/licenses/gpl-2.0.html
26
 */
27
28
namespace PHP_Typography;
29
30
/**
31
 * Some static methods for DOM manipulation.
32
 *
33
 * @since 4.2.0
34
 */
35
abstract class DOM {
36
37
	/**
38
	 * An array of block tag names.
39
	 *
40
	 * @var array
41
	 */
42
	private static $block_tags;
43
44
	/**
45
	 * Retrieves an array of block tag names.
46
	 *
47
	 * @param bool $reset Optional. Default false.
48
	 *
49
	 * @return array
50
	 */
51
	public static function block_tags( $reset = false ) {
52
		if ( empty( self::$block_tags ) || $reset ) {
53
			self::$block_tags = array_merge(
54
				array_flip( array_filter( array_keys( \Masterminds\HTML5\Elements::$html5 ), function( $tag ) {
55
					return \Masterminds\HTML5\Elements::isA( $tag, \Masterminds\HTML5\Elements::BLOCK_TAG );
56
				} ) ),
57
				array_flip( [ 'li', 'td', 'dt' ] ) // not included as "block tags" in current HTML5-PHP version.
58
			);
59
		}
60
61
		return self::$block_tags;
62
	}
63
64
65
	/**
66
	 * Converts \DOMNodeList to array;
67
	 *
68
	 * @param \DOMNodeList $list Required.
69
	 *
70
	 * @return array An associative array in the form ( $spl_object_hash => $node ).
71
	 */
72
	public static function nodelist_to_array( \DOMNodeList $list ) {
73
		$out = [];
74
75
		foreach ( $list as $node ) {
76
			$out[ spl_object_hash( $node ) ] = $node;
77
		}
78
79
		return $out;
80
	}
81
82
	/**
83
	 * Retrieves an array containing all the ancestors of the node. This could be done
84
	 * via an XPath query for "ancestor::*", but DOM walking is in all likelyhood faster.
85
	 *
86
	 * @param \DOMNode $node Required.
87
	 *
88
	 * @return array An array of \DOMNode.
89
	 */
90
	public static function get_ancestors( \DOMNode $node ) {
91
		$result = [];
92
93
		while ( ( $node = $node->parentNode ) && ( $node instanceof \DOMElement ) ) { // @codingStandardsIgnoreLine.
94
			$result[] = $node;
95
		}
96
97
		return $result;
98
	}
99
100
	/**
101
	 * Checks whether the \DOMNode has one of the given classes.
102
	 * If $tag is a \DOMText, the parent DOMElement is checked instead.
103
	 *
104
	 * @param \DOMNode     $tag        An element or textnode.
105
	 * @param string|array $classnames A single classname or an array of classnames.
106
	 *
107
	 * @return boolean True if the element has any of the given class(es).
108
	 */
109
	public static function has_class( \DOMNode $tag, $classnames ) {
110
		if ( $tag instanceof \DOMText ) {
111
			$tag = $tag->parentNode; // @codingStandardsIgnoreLine.
112
		}
113
114
		// Bail if we are not working with a tag or if there is no classname.
115
		if ( ! ( $tag instanceof \DOMElement ) || empty( $classnames ) ) {
116
			return false;
117
		}
118
119
		// Ensure we always have an array of classnames.
120
		if ( ! is_array( $classnames ) ) {
121
			$classnames = [ $classnames ];
122
		}
123
124
		if ( $tag->hasAttribute( 'class' ) ) {
125
			$tag_classes = array_flip( explode( ' ', $tag->getAttribute( 'class' ) ) );
126
127
			foreach ( $classnames as $classname ) {
128
				if ( isset( $tag_classes[ $classname ] ) ) {
129
					return true;
130
				}
131
			}
132
		}
133
134
		return false;
135
	}
136
137
	/**
138
	 * Retrieves the last character of the previous \DOMText sibling (if there is one).
139
	 *
140
	 * @param \DOMNode $node The content node.
141
	 *
142
	 * @return string A single character (or the empty string).
143
	 */
144
	public static function get_prev_chr( \DOMNode $node ) {
145
		return self::get_adjacent_chr( $node, -1, 1, [ __CLASS__, 'get_previous_textnode' ] );
146
	}
147
148
	/**
149
	 * Retrieves the first character of the next \DOMText sibling (if there is one).
150
	 *
151
	 * @param \DOMNode $node The content node.
152
	 *
153
	 * @return string A single character (or the empty string).
154
	 */
155
	public static function get_next_chr( \DOMNode $node ) {
156
		return self::get_adjacent_chr( $node, 0, 1, [ __CLASS__, 'get_next_textnode' ] );
157
	}
158
159
	/**
160
	 * Retrieves a character from the given \DOMNode.
161
	 *
162
	 * @since 5.0.0
163
	 *
164
	 * @param  \DOMNode $node         Required.
165
	 * @param  int      $position     The position parameter for `substr`.
166
	 * @param  int      $length       The length parameter for `substr`.
167
	 * @param  callable $get_textnode A function to retrieve the \DOMText from the node.
168
	 *
169
	 * @return string The character or an empty string.
170
	 */
171
	private static function get_adjacent_chr( \DOMNode $node, $position, $length, callable $get_textnode ) {
172
		$textnode = $get_textnode( $node );
173
174
		if ( isset( $textnode ) && isset( $textnode->data ) ) {
175
			// Determine encoding.
176
			$func = Strings::functions( $textnode->data );
177
178
			if ( ! empty( $func ) ) {
179
				return preg_replace( '/\p{C}/Su', '', $func['substr']( $textnode->data, $position, $length ) );
180
			}
181
		}
182
183
		return '';
184
	}
185
186
	/**
187
	 * Retrieves the previous \DOMText sibling (if there is one).
188
	 *
189
	 * @param \DOMNode|null $node Optional. The content node. Default null.
190
	 *
191
	 * @return \DOMText|null Null if $node is a block-level element or no text sibling exists.
192
	 */
193
	public static function get_previous_textnode( \DOMNode $node = null ) {
194
		return self::get_adjacent_textnode( function( &$another_node = null ) {
195
			$another_node = $another_node->previousSibling;
196
			return self::get_last_textnode( $another_node );
197
		}, __METHOD__, $node );
198
	}
199
200
	/**
201
	 * Retrieves the next \DOMText sibling (if there is one).
202
	 *
203
	 * @param \DOMNode|null $node Optional. The content node. Default null.
204
	 *
205
	 * @return \DOMText|null Null if $node is a block-level element or no text sibling exists.
206
	 */
207
	public static function get_next_textnode( \DOMNode $node = null ) {
208
		return self::get_adjacent_textnode( function( &$another_node = null ) {
209
			$another_node = $another_node->nextSibling;
210
			return self::get_first_textnode( $another_node );
211
		}, __METHOD__, $node );
212
	}
213
214
	/**
215
	 * Retrieves an adjacent \DOMText sibling if there is one.
216
	 *
217
	 * @since 5.0.0
218
	 *
219
	 * @param callable      $iterate             Takes a reference \DOMElement and returns a \DOMText (or null).
220
	 * @param callable      $get_adjacent_parent Takes a single \DOMElement parameter and returns a \DOMText (or null).
221
	 * @param \DOMNode|null $node                Optional. The content node. Default null.
222
	 *
223
	 * @return \DOMText|null Null if $node is a block-level element or no text sibling exists.
224
	 */
225
	private static function get_adjacent_textnode( callable $iterate, callable $get_adjacent_parent, \DOMNode $node = null ) {
226 View Code Duplication
		if ( ! isset( $node ) ) {
227
			return null;
228
		} elseif ( $node instanceof \DOMElement && isset( self::$block_tags[ $node->tagName ] ) ) {
229
			return null;
230
		}
231
232
		/**
233
		 * The result node.
234
		 *
235
		 * @var \DOMText|null
236
		 */
237
		$adjacent      = null;
238
		$iterated_node = $node;
239
240
		// Iterate to find adjacent node.
241
		while ( null !== $iterated_node && null === $adjacent ) {
242
			$adjacent = $iterate( $iterated_node );
243
		}
244
245
		// Last ressort.
246
		if ( null === $adjacent ) {
247
			$adjacent = $get_adjacent_parent( $node->parentNode );
248
		}
249
250
		return $adjacent;
251
	}
252
253
	/**
254
	 * Retrieves the first \DOMText child of the element. Block-level child elements are ignored.
255
	 *
256
	 * @param \DOMNode|null $node      Optional. Default null.
257
	 * @param bool          $recursive Should be set to true on recursive calls. Optional. Default false.
258
	 *
259
	 * @return \DOMText|null The first child of type \DOMText, the element itself if it is of type \DOMText or null.
260
	 */
261 View Code Duplication
	public static function get_first_textnode( \DOMNode $node = null, $recursive = false ) {
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...
262
		return self::get_edge_textnode( function( \DOMNodeList $children, \DOMText &$first_textnode = null ) {
263
			$i = 0;
264
265
			while ( $i < $children->length && empty( $first_textnode ) ) {
266
				$first_textnode = self::get_first_textnode( $children->item( $i ), true );
267
				$i++;
268
			}
269
		}, $node, $recursive );
270
	}
271
272
	/**
273
	 * Retrieves the last \DOMText child of the element. Block-level child elements are ignored.
274
	 *
275
	 * @param \DOMNode|null $node      Optional. Default null.
276
	 * @param bool          $recursive Should be set to true on recursive calls. Optional. Default false.
277
	 *
278
	 * @return \DOMText|null The last child of type \DOMText, the element itself if it is of type \DOMText or null.
279
	 */
280 View Code Duplication
	public static function get_last_textnode( \DOMNode $node = null, $recursive = false ) {
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...
281
		return self::get_edge_textnode( function( \DOMNodeList $children, \DOMText &$last_textnode = null ) {
282
			$i = $children->length - 1;
283
284
			while ( $i >= 0 && empty( $last_textnode ) ) {
285
				$last_textnode = self::get_last_textnode( $children->item( $i ), true );
286
				$i--;
287
			}
288
		}, $node, $recursive );
289
	}
290
291
	/**
292
	 * Retrieves an edge \DOMText child of the element specified by the callable.
293
	 * Block-level child elements are ignored.
294
	 *
295
	 * @since 5.0.0
296
	 *
297
	 * @param callable      $iteration Takes two parameters, a \DOMNodeList and
298
	 *                                 a reference to the \DOMText used as the result.
299
	 * @param \DOMNode|null $node      Optional. Default null.
300
	 * @param bool          $recursive Should be set to true on recursive calls. Optional. Default false.
301
	 *
302
	 * @return \DOMText|null The last child of type \DOMText, the element itself if it is of type \DOMText or null.
303
	 */
304
	private static function get_edge_textnode( callable $iteration, \DOMNode $node = null, $recursive = false ) {
305
		if ( ! isset( $node ) ) {
306
			return null;
307
		}
308
309 View Code Duplication
		if ( $node instanceof \DOMText ) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across 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...
310
			return $node;
311
		} elseif ( ! $node instanceof \DOMElement ) {
312
			// Return null if $node is neither \DOMText nor \DOMElement.
313
			return null;
314
		} elseif ( $recursive && isset( self::$block_tags[ $node->tagName ] ) ) {
315
			return null;
316
		}
317
318
		$edge_textnode = null;
319
320
		if ( $node->hasChildNodes() ) {
321
			$iteration( $node->childNodes, $edge_textnode );
322
		}
323
324
		return $edge_textnode;
325
	}
326
327
	/**
328
	 * Returns the nearest block-level parent (or null).
329
	 *
330
	 * @param \DOMNode $node Required.
331
	 *
332
	 * @return \DOMElement|null
333
	 */
334
	public static function get_block_parent( \DOMNode $node ) {
335
		$parent = $node->parentNode;
336
		if ( ! $parent instanceof \DOMElement ) {
337
			return null;
338
		}
339
340
		while ( ! isset( self::$block_tags[ $parent->tagName ] ) && $parent->parentNode instanceof \DOMElement ) {
341
			/**
342
			 * The parent is sure to be a \DOMElement.
343
			 *
344
			 * @var \DOMElement
345
			 */
346
			$parent = $parent->parentNode;
347
		}
348
349
		return $parent;
350
	}
351
352
	/**
353
	 * Retrieves the tag name of the nearest block-level parent.
354
	 *
355
	 * @param \DOMNode $node A node.
356
357
	 * @return string The tag name (or the empty string).
358
	 */
359
	public static function get_block_parent_name( \DOMNode $node ) {
360
		$parent = self::get_block_parent( $node );
361
362
		if ( ! empty( $parent ) ) {
363
			return $parent->tagName;
364
		} else {
365
			return '';
366
		}
367
	}
368
}
369
370
/**
371
 *  Initialize block tags on load.
372
 */
373
DOM::block_tags(); // @codeCoverageIgnore
374