Completed
Push — master ( af7bae...87972f )
by Carsten
01:23
created

ListTrait   B

Complexity

Total Complexity 47

Size/Duplication

Total Lines 185
Duplicated Lines 12.43 %

Coupling/Cohesion

Components 1
Dependencies 0

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 47
lcom 1
cbo 0
dl 23
loc 185
ccs 78
cts 78
cp 1
rs 8.439
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
A identifyOl() 0 4 4
B identifyUl() 0 6 8
parseBlocks() 0 1 ?
parseInline() 0 1 ?
renderAbsy() 0 1 ?
detectLineType() 0 1 ?
F consumeList() 0 88 28
A renderList() 0 15 3
A generateHtmlAttributes() 0 7 2
A consumeOl() 12 12 1
A consumeUl() 11 11 1

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 ListTrait 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 ListTrait, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @copyright Copyright (c) 2014 Carsten Brandt
4
 * @license https://github.com/cebe/markdown/blob/master/LICENSE
5
 * @link https://github.com/cebe/markdown#readme
6
 */
7
8
namespace cebe\markdown\block;
9
10
/**
11
 * Adds the list blocks
12
 */
13
trait ListTrait
14
{
15
	/**
16
	 * @var bool enable support `start` attribute of ordered lists. This means that lists
17
	 * will start with the number you actually type in markdown and not the HTML generated one.
18
	 * Defaults to `false` which means that numeration of all ordered lists(<ol>) starts with 1.
19
	 */
20
	public $keepListStartNumber = false;
21
22
	/**
23
	 * identify a line as the beginning of an ordered list.
24
	 */
25 198
	protected function identifyOl($line)
26
	{
27 198
		return (($l = $line[0]) > '0' && $l <= '9' || $l === ' ') && preg_match('/^ {0,3}\d+\.[ \t]/', $line);
28
	}
29
30
	/**
31
	 * identify a line as the beginning of an unordered list.
32
	 */
33 199
	protected function identifyUl($line)
34
	{
35 199
		$l = $line[0];
36 199
		return ($l === '-' || $l === '+' || $l === '*') && (isset($line[1]) && (($l1 = $line[1]) === ' ' || $l1 === "\t")) ||
37 199
		       ($l === ' ' && preg_match('/^ {0,3}[\-\+\*][ \t]/', $line));
38
	}
39
40
	/**
41
	 * Consume lines for an ordered list
42
	 */
43 22 View Code Duplication
	protected function consumeOl($lines, $current)
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...
44
	{
45
		// consume until newline
46
47
		$block = [
48 22
			'list',
49
			'list' => 'ol',
50
			'attr' => [],
51
			'items' => [],
52
		];
53 22
		return $this->consumeList($lines, $current, $block, 'ol');
54
	}
55
56
	/**
57
	 * Consume lines for an unordered list
58
	 */
59 36 View Code Duplication
	protected function consumeUl($lines, $current)
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...
60
	{
61
		// consume until newline
62
63
		$block = [
64 36
			'list',
65
			'list' => 'ul',
66
			'items' => [],
67
		];
68 36
		return $this->consumeList($lines, $current, $block, 'ul');
69
	}
70
71 41
	private function consumeList($lines, $current, $block, $type)
72
	{
73 41
		$item = 0;
74 41
		$indent = '';
75 41
		$len = 0;
76 41
		$lastLineEmpty = false;
77
		// track the indentation of list markers, if indented more than previous element
78
		// a list marker is considered to be long to a lower level
79 41
		$leadSpace = 3;
80 41
		$marker = $type === 'ul' ? ltrim($lines[$current])[0] : '';
81 41
		for ($i = $current, $count = count($lines); $i < $count; $i++) {
82 41
			$line = $lines[$i];
83
			// match list marker on the beginning of the line
84 41
			$pattern = ($type == 'ol') ? '/^( {0,'.$leadSpace.'})(\d+)\.[ \t]+/' : '/^( {0,'.$leadSpace.'})\\'.$marker.'[ \t]+/';
85 41
			if (preg_match($pattern, $line, $matches)) {
86 41
				if (($len = substr_count($matches[0], "\t")) > 0) {
87 10
					$indent = str_repeat("\t", $len);
88 10
					$line = substr($line, strlen($matches[0]));
89
				} else {
90 38
					$len = strlen($matches[0]);
91 38
					$indent = str_repeat(' ', $len);
92 38
					$line = substr($line, $len);
93
				}
94 41
				if ($i === $current) {
95 41
					$leadSpace = strlen($matches[1]) + 1;
96
				}
97
98 41
				if ($type == 'ol' && $this->keepListStartNumber) {
99
					// attr `start` for ol
100 2
					if (!isset($block['attr']['start']) && isset($matches[2])) {
101 2
						$block['attr']['start'] = $matches[2];
102
					}
103
				}
104
105 41
				$block['items'][++$item][] = $line;
106 41
				$block['lazyItems'][$item] = $lastLineEmpty;
107 41
				$lastLineEmpty = false;
108 38
			} elseif (ltrim($line) === '') {
109
				// line is empty, may be a lazy list
110 38
				$lastLineEmpty = true;
111
112
				// two empty lines will end the list
113 38
				if (!isset($lines[$i + 1][0])) {
114 23
					break;
115
116
				// next item is the continuation of this list -> lazy list
117 38
				} elseif (preg_match($pattern, $lines[$i + 1])) {
118 18
					$block['items'][$item][] = $line;
119 18
					$block['lazyItems'][$item] = true;
120
121
				// next item is indented as much as this list -> lazy list if it is not a reference
122 35
				} elseif (strncmp($lines[$i + 1], $indent, $len) === 0 || !empty($lines[$i + 1]) && $lines[$i + 1][0] == "\t") {
123 18
					$block['items'][$item][] = $line;
124 18
					$nextLine = $lines[$i + 1][0] === "\t" ? substr($lines[$i + 1], 1) : substr($lines[$i + 1], $len);
125 18
					$block['lazyItems'][$item] = empty($nextLine) || !method_exists($this, 'identifyReference') || !$this->identifyReference($nextLine);
126
127
				// everything else ends the list
128
				} else {
129 38
					break;
130
				}
131
			} else {
132 28
				if ($line[0] === "\t") {
133 7
					$line = substr($line, 1);
134 24
				} elseif (strncmp($line, $indent, $len) === 0) {
135 21
					$line = substr($line, $len);
136
				}
137 28
				$block['items'][$item][] = $line;
138 28
				$lastLineEmpty = false;
139
			}
140
		}
141
142 41
		foreach($block['items'] as $itemId => $itemLines) {
143 41
			$content = [];
144 41
			if (!$block['lazyItems'][$itemId]) {
145 35
				$firstPar = [];
146 35
				while (!empty($itemLines) && rtrim($itemLines[0]) !== '' && $this->detectLineType($itemLines, 0) === 'paragraph') {
147 35
					$firstPar[] = array_shift($itemLines);
148
				}
149 35
				$content = $this->parseInline(implode("\n", $firstPar));
150
			}
151 41
			if (!empty($itemLines)) {
152 28
				$content = array_merge($content, $this->parseBlocks($itemLines));
153
			}
154 41
			$block['items'][$itemId] = $content;
155
		}
156
157 41
		return [$block, $i];
158
	}
159
160
	/**
161
	 * Renders a list
162
	 */
163 41
	protected function renderList($block)
164
	{
165 41
		$type = $block['list'];
166
167 41
		if (!empty($block['attr'])) {
168 2
			$output = "<$type " . $this->generateHtmlAttributes($block['attr']) . ">\n";
169
		} else {
170 40
			$output = "<$type>\n";
171
		}
172
173 41
		foreach ($block['items'] as $item => $itemLines) {
174 41
			$output .= '<li>' . $this->renderAbsy($itemLines). "</li>\n";
175
		}
176 41
		return $output . "</$type>\n";
177
	}
178
179
180
	/**
181
	 * Return html attributes string from [attrName => attrValue] list
182
	 * @param array $attributes the attribute name-value pairs.
183
	 * @return string
184
	 */
185 2
	private function generateHtmlAttributes($attributes)
186
	{
187 2
		foreach ($attributes as $name => $value) {
188 2
			$attributes[$name] = "$name=\"$value\"";
189
		}
190 2
		return implode(' ', $attributes);
191
	}
192
193
	abstract protected function parseBlocks($lines);
194
	abstract protected function parseInline($text);
195
	abstract protected function renderAbsy($absy);
196
	abstract protected function detectLineType($lines, $current);
197
}
198