1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace ArjanSchouten\HtmlMinifier\Placeholders; |
4
|
|
|
|
5
|
|
|
use ArjanSchouten\HtmlMinifier\PlaceholderContainer; |
6
|
|
|
|
7
|
|
|
class WhitespacePlaceholder implements PlaceholderInterface |
8
|
|
|
{ |
9
|
|
|
/** |
10
|
|
|
* @var array |
11
|
|
|
*/ |
12
|
|
|
protected $htmlPlaceholderTags = [ |
13
|
|
|
'plaintext', |
14
|
|
|
'textarea', |
15
|
|
|
'listing', |
16
|
|
|
'script', |
17
|
|
|
'style', |
18
|
|
|
'code', |
19
|
|
|
'cite', |
20
|
|
|
'pre', |
21
|
|
|
'xmp', |
22
|
|
|
]; |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* Replace critical content with a temporary placeholder. |
26
|
|
|
* |
27
|
|
|
* @param \ArjanSchouten\HtmlMinifier\MinifyContext $context |
28
|
|
|
* |
29
|
|
|
* @return \ArjanSchouten\HtmlMinifier\MinifyContext |
30
|
|
|
*/ |
31
|
5 |
|
public function process($context) |
32
|
|
|
{ |
33
|
5 |
|
$contents = $context->getContents(); |
34
|
|
|
|
35
|
5 |
|
$contents = $this->whitespaceBetweenInlineElements($contents, $context->getPlaceholderContainer()); |
36
|
5 |
|
$contents = $this->whitespaceInInlineElements($contents, $context->getPlaceholderContainer()); |
37
|
5 |
|
$contents = $this->replaceElementContents($contents, $context->getPlaceholderContainer()); |
38
|
|
|
|
39
|
5 |
|
return $context->setContents($contents); |
40
|
|
|
} |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* Whitespaces between inline html elements must be replaced with a placeholder because |
44
|
|
|
* a browser is showing that whitespace. |
45
|
|
|
* |
46
|
|
|
* @param string $contents |
47
|
|
|
* @param \ArjanSchouten\HtmlMinifier\PlaceholderContainer $placeholderContainer |
48
|
|
|
* |
49
|
|
|
* @return string |
50
|
|
|
*/ |
51
|
5 |
View Code Duplication |
protected function whitespaceBetweenInlineElements($contents, PlaceholderContainer $placeholderContainer) |
|
|
|
|
52
|
|
|
{ |
53
|
5 |
|
$elementsRegex = $this->getInlineElementsRegex(); |
54
|
|
|
|
55
|
5 |
|
return preg_replace_callback( |
56
|
|
|
'/ |
57
|
|
|
( |
58
|
5 |
|
<('.$elementsRegex.') # Match the start tag and capture it |
59
|
|
|
(?:(?!<\/\2>).*) # Match everything without the end tag |
60
|
|
|
<\/\2> # Match the captured elements end tag |
61
|
|
|
) |
62
|
|
|
\s+ # Match minimal 1 whitespace between the elements |
63
|
5 |
|
<('.$elementsRegex.') # Match the start of the next inline element |
64
|
|
|
/xi', |
65
|
|
|
function ($match) use ($placeholderContainer) { |
66
|
|
|
// Where going to respect one space between the inline elements. |
67
|
1 |
|
$placeholder = $placeholderContainer->createPlaceholder(' '); |
68
|
|
|
|
69
|
1 |
|
return $match[1].$placeholder.'<'.$match[3]; |
70
|
5 |
|
}, $contents); |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* Whitespaces in an inline element have a function so we replace it. |
75
|
|
|
* |
76
|
|
|
* @param string $contents |
77
|
|
|
* @param \ArjanSchouten\HtmlMinifier\PlaceholderContainer $placeholderContainer |
78
|
|
|
* |
79
|
|
|
* @return string |
80
|
|
|
*/ |
81
|
5 |
View Code Duplication |
protected function whitespaceInInlineElements($contents, PlaceholderContainer $placeholderContainer) |
|
|
|
|
82
|
|
|
{ |
83
|
5 |
|
$elementsRegex = $this->getInlineElementsRegex(); |
84
|
|
|
|
85
|
5 |
|
return preg_replace_callback( |
86
|
|
|
'/ |
87
|
|
|
( |
88
|
5 |
|
<('.$elementsRegex.') # Match an inline element |
89
|
|
|
(?:(?!<\/\2>).*) # Match everything except its end tag |
90
|
|
|
<\/\2> # Match the end tag |
91
|
|
|
\s+ |
92
|
|
|
) |
93
|
5 |
|
<('.$elementsRegex.') # Match starting tag |
94
|
|
|
/xis', |
95
|
|
|
function ($match) use ($placeholderContainer) { |
96
|
1 |
|
return $this->replaceWhitespacesInInlineElements($match[1], $placeholderContainer).'<'.$match[3]; |
97
|
5 |
|
}, $contents); |
98
|
|
|
} |
99
|
|
|
|
100
|
|
|
/** |
101
|
|
|
* Replace the whitespaces in inline elements with a placeholder. |
102
|
|
|
* |
103
|
|
|
* @param string $element |
104
|
|
|
* @param \ArjanSchouten\HtmlMinifier\PlaceholderContainer $placeholderContainer |
105
|
|
|
* |
106
|
|
|
* @return string |
107
|
|
|
*/ |
108
|
1 |
|
private function replaceWhitespacesInInlineElements($element, PlaceholderContainer $placeholderContainer) |
109
|
|
|
{ |
110
|
|
|
return preg_replace_callback('/>\s/', function ($match) use ($placeholderContainer) { |
|
|
|
|
111
|
1 |
|
return '>'.$placeholderContainer->createPlaceholder(' '); |
112
|
1 |
|
}, $element); |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
/** |
116
|
|
|
* @param string $contents |
117
|
|
|
* @param \ArjanSchouten\HtmlMinifier\PlaceholderContainer $placeholderContainer |
118
|
|
|
* |
119
|
|
|
* @return string |
120
|
|
|
*/ |
121
|
5 |
|
protected function replaceElementContents($contents, PlaceholderContainer $placeholderContainer) |
122
|
|
|
{ |
123
|
5 |
|
$htmlTags = implode('|', $this->htmlPlaceholderTags); |
124
|
|
|
|
125
|
|
|
$pattern = '/ |
126
|
|
|
( |
127
|
5 |
|
<(' . $htmlTags . ') # Match html start tag and capture |
128
|
|
|
(?: |
129
|
|
|
[^"\'>]*|"[^"]*"|\'[^\']*\' |
130
|
|
|
)* # Match all attributes |
131
|
|
|
> # Match end of tag |
132
|
|
|
) |
133
|
|
|
( |
134
|
|
|
(?: # Negotiate this part |
135
|
|
|
(?!<\/\2>) # Negative look ahead for the end tag |
136
|
|
|
. # Match everything if not the end tag |
137
|
|
|
)*+ # Possessive quantifier which will prevent backtracking |
138
|
|
|
) |
139
|
|
|
(<\/\2>) # Match end tag by back referencing the start tag |
140
|
|
|
/xis'; |
141
|
|
|
|
142
|
5 |
|
return preg_replace_callback($pattern, function ($match) use ($placeholderContainer) { |
143
|
1 |
|
return $match[1].$placeholderContainer->createPlaceholder($match[3]).$match[4]; |
144
|
5 |
|
}, $contents); |
145
|
|
|
} |
146
|
|
|
|
147
|
|
|
/** |
148
|
|
|
* Get the regular expression for matching inline elements. |
149
|
|
|
* |
150
|
|
|
* @return string |
151
|
|
|
*/ |
152
|
5 |
|
private function getInlineElementsRegex() |
153
|
|
|
{ |
154
|
5 |
|
return 'a(?:bbr|cronym)?|b(?:utton|r|ig|do)?|c(?:ite|ode)|dfn|em|i(?:mg|nput)|kbd|label|map|object|q|s(?:amp|elect|mall|pan|trong|u[bp])|t(?:extarea|t)|var'; |
155
|
|
|
} |
156
|
|
|
} |
157
|
|
|
|
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.