Completed
Push — master ( a44fae...42eb55 )
by Carsten
02:28
created

Markdown   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 342
Duplicated Lines 4.09 %

Coupling/Cohesion

Components 3
Dependencies 10

Test Coverage

Coverage 82.5%

Importance

Changes 0
Metric Value
wmc 50
lcom 3
cbo 10
dl 14
loc 342
ccs 99
cts 120
cp 0.825
rs 8.6206
c 0
b 0
f 0

22 Methods

Rating   Name   Duplication   Size   Complexity  
A prepare() 0 5 1
B consumeParagraph() 0 21 8
A renderParagraph() 0 4 1
A renderQuote() 0 4 1
A renderCode() 0 10 2
A renderList() 0 11 3
A renderHeadline() 0 10 4
A renderHr() 0 4 1
C renderLink() 7 23 7
A renderImage() 7 22 3
A parseInlineHtml() 0 13 3
A renderLabel() 0 4 1
A renderEmail() 0 5 1
A renderUrl() 0 4 1
A renderInlineCode() 0 11 2
A renderStrong() 0 4 1
A renderEmph() 0 4 1
A parseEscape() 0 10 4
A renderBackslash() 0 4 1
A escapeUrl() 0 4 1
A escapeLatex() 0 7 2
A renderText() 0 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 Markdown 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 Markdown, 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\latex;
9
10
use cebe\markdown\block\CodeTrait;
11
use cebe\markdown\block\HeadlineTrait;
12
use cebe\markdown\block\ListTrait;
13
use cebe\markdown\block\QuoteTrait;
14
use cebe\markdown\block\RuleTrait;
15
16
use cebe\markdown\inline\CodeTrait as InlineCodeTrait;
17
use cebe\markdown\inline\EmphStrongTrait;
18
use cebe\markdown\inline\LinkTrait;
19
20
use MikeVanRiel\TextToLatex;
21
22
/**
23
 * Markdown parser for the [initial markdown spec](http://daringfireball.net/projects/markdown/syntax).
24
 *
25
 * @author Carsten Brandt <[email protected]>
26
 */
27
class Markdown extends \cebe\markdown\Parser
28
{
29
	// include block element parsing using traits
30
	use CodeTrait;
31
	use HeadlineTrait;
32
	use ListTrait {
33
		// Check Ul List before headline
34
		identifyUl as protected identifyBUl;
35
		consumeUl as protected consumeBUl;
36
	}
37
	use QuoteTrait;
38
	use RuleTrait {
39
		// Check Hr before checking lists
40
		identifyHr as protected identifyAHr;
41
		consumeHr as protected consumeAHr;
42
	}
43
44
	// include inline element parsing using traits
45
	use InlineCodeTrait;
46
	use EmphStrongTrait;
47
	use LinkTrait;
48
49
	/**
50
	 * @var string this string will be prefixed to all auto generated labels.
51
	 * This can be used to disambiguate labels when combining multiple markdown files into one document.
52
	 */
53
	public $labelPrefix = '';
54
55
	/**
56
	 * @var array these are "escapeable" characters. When using one of these prefixed with a
57
	 * backslash, the character will be outputted without the backslash and is not interpreted
58
	 * as markdown.
59
	 */
60
	protected $escapeCharacters = [
61
		'\\', // backslash
62
		'`', // backtick
63
		'*', // asterisk
64
		'_', // underscore
65
		'{', '}', // curly braces
66
		'[', ']', // square brackets
67
		'(', ')', // parentheses
68
		'#', // hash mark
69
		'+', // plus sign
70
		'-', // minus sign (hyphen)
71
		'.', // dot
72
		'!', // exclamation mark
73
		'<', '>',
74
	];
75
76
77
	/**
78
	 * @inheritDoc
79
	 */
80 23
	protected function prepare()
81
	{
82
		// reset references
83 23
		$this->references = [];
84 23
	}
85
86
	/**
87
	 * Consume lines for a paragraph
88
	 *
89
	 * Allow headlines and code to break paragraphs
90
	 */
91 8
	protected function consumeParagraph($lines, $current)
92
	{
93
		// consume until newline
94 8
		$content = [];
95 8
		for ($i = $current, $count = count($lines); $i < $count; $i++) {
96 8
			$line = $lines[$i];
97 8
			if (!empty($line) && ltrim($line) !== '' &&
98 8
				!($line[0] === "\t" || $line[0] === " " && strncmp($line, '    ', 4) === 0) &&
99 8
				!$this->identifyHeadline($line, $lines, $i))
100 8
			{
101 8
				$content[] = $line;
102 8
			} else {
103 7
				break;
104
			}
105 8
		}
106
		$block = [
107 8
			'paragraph',
108 8
			'content' => $this->parseInline(implode("\n", $content)),
109 8
		];
110 8
		return [$block, --$i];
111
	}
112
113
114
	// rendering adjusted for LaTeX output
115
116
117
	/**
118
	 * @inheritdoc
119
	 */
120 21
	protected function renderParagraph($block)
121
	{
122 21
		return $this->renderAbsy($block['content']) . "\n\n";
123
	}
124
125
	/**
126
	 * @inheritdoc
127
	 */
128 2
	protected function renderQuote($block)
129
	{
130 2
		return '\begin{quote}' . $this->renderAbsy($block['content']) . "\\end{quote}\n";
131
	}
132
133
	/**
134
	 * @inheritdoc
135
	 */
136 3
	protected function renderCode($block)
137
	{
138 3
		$language = isset($block['language']) ? "\\lstset{language={$block['language']}}" : '\lstset{language={}}';
139
140 3
		$content = $block['content'];
141
		// replace No-Break Space characters in code block, which do not render in LaTeX
142 3
		$content = preg_replace("/[\x{00a0}\x{202f}]/u", ' ', $content);
143
144 3
		return "$language\\begin{lstlisting}\n{$content}\n\\end{lstlisting}\n";
145
	}
146
147
	/**
148
	 * @inheritdoc
149
	 */
150 2
	protected function renderList($block)
151
	{
152 2
		$type = ($block['list'] === 'ol') ? 'enumerate' : 'itemize';
153 2
		$output = "\\begin{{$type}}\n";
154
155 2
		foreach ($block['items'] as $item => $itemLines) {
156 2
			$output .= '\item ' . $this->renderAbsy($itemLines). "\n";
157 2
		}
158
159 2
		return "$output\\end{{$type}}\n";
160
	}
161
162
	/**
163
	 * @inheritdoc
164
	 */
165 3
	protected function renderHeadline($block)
166
	{
167 3
		$content = $this->renderAbsy($block['content']);
168 3
		switch($block['level']) {
169 3
			case 1: return "\\section{{$content}}\n";
170 3
			case 2: return "\\subsection{{$content}}\n";
171 2
			case 3: return "\\subsubsection{{$content}}\n";
172 2
			default: return "\\paragraph{{$content}}\n";
173 2
		}
174
	}
175
176
	/**
177
	 * @inheritdoc
178
	 */
179 2
	protected function renderHr($block)
0 ignored issues
show
Unused Code introduced by
The parameter $block is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
180
	{
181 2
		return "\n\\noindent\\rule{\\textwidth}{0.4pt}\n";
182
	}
183
184
	/**
185
	 * @inheritdoc
186
	 */
187 2
	protected function renderLink($block)
188
	{
189 2 View Code Duplication
		if (isset($block['refkey'])) {
0 ignored issues
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...
190
			if (($ref = $this->lookupReference($block['refkey'])) !== false) {
191
				$block = array_merge($block, $ref);
192
			} else {
193
				return $block['orig'];
194
			}
195
		}
196
197 2
		$url = $block['url'];
198 2
		$text = $this->renderAbsy($block['text']);
199 2
		if (strpos($url, '://') === false) {
200
			// consider all non absolute links as relative in the document
201
			// $title is ignored in this case.
202
			if (isset($url[0]) && $url[0] === '#') {
203
				$url = $this->labelPrefix . $url;
204
			}
205
			return '\hyperref['.str_replace('#', '::', $url).']{' . $text . '}';
206
		} else {
207 2
			return $text . '\\footnote{' . (empty($block['title']) ? '' : $this->escapeLatex($block['title']) . ': ') . '\url{' . $this->escapeUrl($url) . '}}';
208
		}
209
	}
210
211
	/**
212
	 * @inheritdoc
213
	 */
214 2
	protected function renderImage($block)
215
	{
216 2 View Code Duplication
		if (isset($block['refkey'])) {
0 ignored issues
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...
217
			if (($ref = $this->lookupReference($block['refkey'])) !== false) {
218
				$block = array_merge($block, $ref);
219
			} else {
220
				return $block['orig'];
221
			}
222
		}
223
224
		// TODO create figure with caption with title
225
		$replaces = [
226 2
			'%' => '\\%',
227 2
			'{' => '\\%7B',
228 2
			'}' => '\\%7D',
229 2
			'\\' => '\\\\',
230 2
			'#' => '\\#',
231 2
			'$' => '\\%24',
232 2
		];
233 2
		$url = str_replace(array_keys($replaces), array_values($replaces), $block['url']);
234 2
		return "\\noindent\\includegraphics[width=\\textwidth]{{$url}}";
235
	}
236
237
	/**
238
	 * Parses <a name="..."></a> tags as reference labels
239
	 */
240 2
	private function parseInlineHtml($text)
241
	{
242 2
		if (strpos($text, '>') !== false) {
243
			// convert a name markers to \labels
244 2
			if (preg_match('~^<((a|span)) (name|id)="(.*?)">.*?</\1>~i', $text, $matches)) {
245
				return [
246 2
					['label', 'name' => str_replace('#', '::', $this->labelPrefix . $matches[4])],
247 2
					strlen($matches[0])
248 2
				];
249
			}
250
		}
251
		return [['text', '<'], 1];
252
	}
253
254
	/**
255
	 * renders a reference label
256
	 */
257 2
	protected function renderLabel($block)
258
	{
259 2
		return "\\label{{$block['name']}}";
260
	}
261
262
	/**
263
	 * @inheritdoc
264
	 */
265 2
	protected function renderEmail($block)
266
	{
267 2
		$email = $this->escapeUrl($block[1]);
268 2
		return "\\href{mailto:{$email}}{{$email}}";
269
	}
270
271
	/**
272
	 * @inheritdoc
273
	 */
274 2
	protected function renderUrl($block)
275
	{
276 2
		return '\url{' . $this->escapeUrl($block[1]) . '}';
277
	}
278
279
	/**
280
	 * @inheritdoc
281
	 */
282 1
	protected function renderInlineCode($block)
283
	{
284
		// replace No-Break Space characters in code block, which do not render in LaTeX
285 1
		$content = preg_replace("/[\x{00a0}\x{202f}]/u", ' ', $block[1]);
286
287 1
		if (strpos($content, '|') !== false) {
288 1
			return '\\lstinline`' . str_replace("\n", ' ', $content) . '`'; // TODO make this more robust against code containing backticks
289
		} else {
290
			return '\\lstinline|' . str_replace("\n", ' ', $content) . '|';
291
		}
292
	}
293
294
	/**
295
	 * @inheritdoc
296
	 */
297 1
	protected function renderStrong($block)
298
	{
299 1
		return '\textbf{' . $this->renderAbsy($block[1]) . '}';
300
	}
301
302
	/**
303
	 * @inheritdoc
304
	 */
305 1
	protected function renderEmph($block)
306
	{
307 1
		return '\textit{' . $this->renderAbsy($block[1]) . '}';
308
	}
309
310
	/**
311
	 * Parses escaped special characters.
312
	 * This allow a backslash to be interpreted as LaTeX
313
	 * @marker \
314
	 */
315 1
	protected function parseEscape($text)
316
	{
317 1
		if (isset($text[1]) && in_array($text[1], $this->escapeCharacters)) {
318 1
			if ($text[1] === '\\') {
319
				return [['backslash'], 2];
0 ignored issues
show
Best Practice introduced by
The expression return array(array('backslash'), 2); seems to be an array, but some of its elements' types (string[]) are incompatible with the return type of the parent method cebe\markdown\Parser::parseEscape of type array<array|integer>.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
320
			}
321 1
			return [['text', $text[1]], 2];
322
		}
323
		return [['text', $text[0]], 1];
324
	}
325
326
	protected function renderBackslash()
327
	{
328
		return '\\';
329
	}
330
331
	private $_escaper;
332
333
	/**
334
	 * Escape special characters in URLs
335
	 */
336 4
	protected function escapeUrl($string)
337
	{
338 4
		return str_replace('%', '\\%', $this->escapeLatex($string));
339
	}
340
341
	/**
342
	 * Escape special LaTeX characters
343
	 */
344 21
	protected function escapeLatex($string)
345
	{
346 21
		if ($this->_escaper === null) {
347 21
			$this->_escaper = new TextToLatex();
348 21
		}
349 21
		return $this->_escaper->convert($string);
350
	}
351
352
	/**
353
	 * @inheritdocs
354
	 *
355
	 * Parses a newline indicated by two spaces on the end of a markdown line.
356
	 */
357 21
	protected function renderText($text)
358
	{
359 21
		$output = str_replace("  \n", "\\\\\n", $this->escapeLatex($text[1]));
360
		// support No-Break Space in LaTeX
361 21
		$output = preg_replace("/\x{00a0}/u", '~', $output);
362
		// support Narrow No-Break Space spaces in LaTeX
363
		// http://unicode-table.com/en/202F/
364
		// http://tex.stackexchange.com/questions/76132/how-to-typeset-a-small-non-breaking-space
365 21
		$output = preg_replace("/\x{202f}/u", '\nobreak\hspace{.16667em plus .08333em}', $output);
366 21
		return $output;
367
	}
368
}
369