PHPTAL_PreFilter_Compress   F
last analyzed

Complexity

Total Complexity 62

Size/Duplication

Total Lines 263
Duplicated Lines 4.18 %

Coupling/Cohesion

Components 2
Dependencies 4

Importance

Changes 0
Metric Value
dl 11
loc 263
rs 3.44
c 0
b 0
f 0
wmc 62
lcom 2
cbo 4

7 Methods

Rating   Name   Duplication   Size   Complexity  
F filterDOM() 11 103 27
A hasNoInterelementSpace() 0 11 6
A breaksLine() 0 17 5
A isInlineBlock() 0 9 3
A normalizeAttributes() 0 15 3
A compareQNames() 0 12 6
C elementSpecificOptimizations() 0 36 12

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

1
<?php
2
/**
3
 * PHPTAL templating engine
4
 *
5
 * PHP Version 5
6
 *
7
 * @category HTML
8
 * @package  PHPTAL
9
 * @author   Kornel Lesiński <[email protected]>
10
 * @license  http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License
11
 * @version  SVN: $Id: $
12
 * @link     http://phptal.org/
13
 */
14
15
/**
16
 * Removes all unnecessary whitespace from XHTML documents.
17
 *
18
 * extends Normalize only to re-use helper methods
19
 */
20
class PHPTAL_PreFilter_Compress extends PHPTAL_PreFilter_Normalize
21
{
22
    /**
23
     * keeps track whether last element had trailing whitespace (or didn't need it).
24
     * If had_space==false, next element must keep leading space.
25
     */
26
    private $had_space=false;
27
28
    /**
29
     * last text node before closing tag that may need trailing whitespace trimmed.
30
     * It's often last-child, but comments, multiple end tags make that trickier.
31
     */
32
    private $most_recent_text_node=null;
33
34
    function filterDOM(PHPTAL_Dom_Element $root)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
35
    {
36
        // let xml:space=preserve preserve everything
37
        if ($root->getAttributeNS("http://www.w3.org/XML/1998/namespace", 'space') == 'preserve') {
38
            $this->most_recent_text_node = null;
39
            $this->findElementToFilter($root);
40
            return;
41
        }
42
43
        // tal:replace makes element behave like text
44
        if ($root->getAttributeNS('http://xml.zope.org/namespaces/tal','replace')) {
45
            $this->most_recent_text_node = null;
46
            $this->had_space = false;
47
            return;
48
        }
49
50
        $this->normalizeAttributes($root);
51
        $this->elementSpecificOptimizations($root);
52
53
        // <head>, <tr> don't have any significant whitespace
54
        $no_spaces = $this->hasNoInterelementSpace($root);
55
56
        // mostly block-level elements
57
        // if element is conditional, it may not always break the line
58
        $breaks_line = $no_spaces || ($this->breaksLine($root) && !$root->getAttributeNS('http://xml.zope.org/namespaces/tal','condition'));
59
60
        // start tag newline
61
        if ($breaks_line) {
62 View Code Duplication
            if ($this->most_recent_text_node) {
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...
63
                $this->most_recent_text_node->setValueEscaped(rtrim($this->most_recent_text_node->getValueEscaped()));
64
                $this->most_recent_text_node = null;
65
            }
66
            $this->had_space = true;
67
        } else if ($this->isInlineBlock($root)) {
68
            // spaces around <img> must be kept
69
            $this->most_recent_text_node = null;
70
            $this->had_space = false;
71
        }
72
73
        // <pre>, <textarea> are handled separately from xml:space, because they may have attributes normalized
74
        if ($this->isSpaceSensitiveInXHTML($root)) {
75
            $this->most_recent_text_node = null;
76
77
            // HTML 5 (9.1.2.5) specifies quirk that a first *single* newline in <pre> can be removed
78
            if (count($root->childNodes) && $root->childNodes[0] instanceof PHPTAL_Dom_Text) {
79
                if (preg_match('/^\n[^\n]/', $root->childNodes[0]->getValueEscaped())) {
80
                    $root->childNodes[0]->setValueEscaped(substr($root->childNodes[0]->getValueEscaped(),1));
81
                }
82
            }
83
            $this->findElementToFilter($root);
84
            return;
85
        }
86
87
        foreach ($root->childNodes as $node) {
88
89
            if ($node instanceof PHPTAL_Dom_Text) {
90
                // replaces runs of whitespace with ' '
91
                $norm = $this->normalizeSpace($node->getValueEscaped(), $node->getEncoding());
92
93
                if ($no_spaces) {
94
                    $norm = trim($norm);
95
                } elseif ($this->had_space) {
96
                    $norm = ltrim($norm);
97
                }
98
99
                $node->setValueEscaped($norm);
100
101
                // collapsed whitespace-only nodes are ignored (otherwise trimming of most_recent_text_node would be useless)
102
                if ($norm !== '') {
103
                    $this->most_recent_text_node = $node;
104
                    $this->had_space = (substr($norm,-1) == ' ');
105
                }
106
            } else if ($node instanceof PHPTAL_Dom_Element) {
107
                $this->filterDOM($node);
108
            } else if ($node instanceof PHPTAL_Dom_DocumentType || $node instanceof PHPTAL_Dom_XMLDeclaration) {
109
                $this->had_space = true;
110
            } else if ($node instanceof PHPTAL_Dom_ProcessingInstruction) {
111
                // PI may output something requiring spaces
112
                $this->most_recent_text_node = null;
113
                $this->had_space = false;
114
            }
115
        }
116
117
        // repeated element may need trailing space.
118
        if (!$breaks_line && $root->getAttributeNS('http://xml.zope.org/namespaces/tal','repeat')) {
119
            $this->most_recent_text_node = null;
120
        }
121
122
        // tal:content may replace element with something without space
123
        if (!$breaks_line && $root->getAttributeNS('http://xml.zope.org/namespaces/tal','content')) {
124
            $this->had_space = false;
125
            $this->most_recent_text_node = null;
126
        }
127
128
        // line break caused by end tag
129 View Code Duplication
        if ($breaks_line) {
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...
130
            if ($this->most_recent_text_node) {
131
                $this->most_recent_text_node->setValueEscaped(rtrim($this->most_recent_text_node->getValueEscaped()));
132
                $this->most_recent_text_node = null;
133
            }
134
            $this->had_space = true;
135
        }
136
    }
137
138
    private static $no_interelement_space = array(
139
        'html','head','table','thead','tfoot','select','optgroup','dl','ol','ul','tr','datalist',
140
    );
141
142
    private function hasNoInterelementSpace(PHPTAL_Dom_Element $element)
143
    {
144
        if ($element->getLocalName() === 'block'
145
            && $element->parentNode
146
            && $element->getNamespaceURI() === 'http://xml.zope.org/namespaces/tal') {
147
            return $this->hasNoInterelementSpace($element->parentNode);
148
        }
149
150
        return in_array($element->getLocalName(), self::$no_interelement_space)
151
            && ($element->getNamespaceURI() === 'http://www.w3.org/1999/xhtml' || $element->getNamespaceURI() === '');
152
    }
153
154
    /**
155
     *  li is deliberately omitted, as it's commonly used with display:inline in menus.
156
     */
157
    private static $breaks_line = array(
158
        'address','article','aside','base','blockquote','body','br','dd','div','dl','dt','fieldset','figure',
159
        'footer','form','h1','h2','h3','h4','h5','h6','head','header','hgroup','hr','html','legend','link',
160
        'meta','nav','ol','option','p','param','pre','section','style','table','tbody','td','th','thead',
161
        'title','tr','ul','details',
162
    );
163
164
    private function breaksLine(PHPTAL_Dom_Element $element)
165
    {
166
        if ($element->getAttributeNS('http://xml.zope.org/namespaces/metal','define-macro')) {
167
            return true;
168
        }
169
170
        if (!$element->parentNode) {
171
            return true;
172
        }
173
174
        if ($element->getNamespaceURI() !== 'http://www.w3.org/1999/xhtml'
175
	        && $element->getNamespaceURI() !== '') {
176
	        return false;
177
        }
178
179
        return in_array($element->getLocalName(), self::$breaks_line);
180
    }
181
182
    /**
183
     *  replaced elements need to preserve spaces before and after
184
     */
185
    private static $inline_blocks = array(
186
        'select','input','button','img','textarea','output','progress','meter',
187
    );
188
189
    private function isInlineBlock(PHPTAL_Dom_Element $element)
190
    {
191
        if ($element->getNamespaceURI() !== 'http://www.w3.org/1999/xhtml'
192
	        && $element->getNamespaceURI() !== '') {
193
	        return false;
194
        }
195
196
        return in_array($element->getLocalName(), self::$inline_blocks);
197
    }
198
199
    /**
200
     * Consistent sorting of attributes might give slightly better gzip performance
201
     */
202
    protected function normalizeAttributes(PHPTAL_Dom_Element $element)
203
    {
204
        parent::normalizeAttributes($element);
205
206
        $attrs_by_qname = array();
207
        foreach ($element->getAttributeNodes() as $attrnode) {
208
            // safe, as there can't be two attrs with same qname
209
            $attrs_by_qname[$attrnode->getQualifiedName()] = $attrnode;
210
        }
211
212
	if (count($attrs_by_qname) > 1) {
213
		uksort($attrs_by_qname, array($this, 'compareQNames'));
214
		$element->setAttributeNodes(array_values($attrs_by_qname));
215
	}
216
    }
217
218
    /**
219
	 * pre-defined order of attributes roughly by popularity
220
	 */
221
	private static $attributes_order = array(
222
        'href','src','class','rel','type','title','width','height','alt','content','name','style','lang','id',
223
    );
224
225
	/**
226
	 * compare names according to $attributes_order array.
227
	 * Elements that are not in array, are considered greater than all elements in array,
228
	 * and are sorted alphabetically.
229
	 */
230
	private static function compareQNames($a, $b) {
231
		$a_index = array_search($a, self::$attributes_order);
232
		$b_index = array_search($b, self::$attributes_order);
233
234
		if ($a_index !== false && $b_index !== false) {
235
			return $a_index - $b_index;
236
		}
237
		if ($a_index === false && $b_index === false) {
238
			return strcmp($a, $b);
239
		}
240
		return ($a_index === false) ? 1 : -1;
241
	}
242
243
    /**
244
     * HTML5 doesn't care about boilerplate
245
     */
246
	private function elementSpecificOptimizations(PHPTAL_Dom_Element $element)
247
	{
248
	    if ($element->getNamespaceURI() !== 'http://www.w3.org/1999/xhtml'
249
	     && $element->getNamespaceURI() !== '') {
250
	        return;
251
        }
252
253
        if ($this->getPHPTAL()->getOutputMode() !== PHPTAL::HTML5) {
254
            return;
255
        }
256
257
        // <meta charset>
258
	    if ('meta' === $element->getLocalName() &&
259
	        $element->getAttributeNS('','http-equiv') === 'Content-Type') {
260
	            $element->removeAttributeNS('','http-equiv');
261
	            $element->removeAttributeNS('','content');
262
	            $element->setAttributeNS('','charset',strtolower($this->getPHPTAL()->getEncoding()));
263
        }
264
        elseif (('link' === $element->getLocalName() && $element->getAttributeNS('','rel') === 'stylesheet') ||
265
            ('style' === $element->getLocalName())) {
266
            // There's only one type of stylesheets that works.
267
            $element->removeAttributeNS('','type');
268
269
        } elseif ('script' === $element->getLocalName()) {
270
            $element->removeAttributeNS('','language');
271
272
            // Only remove type that matches default. E4X, vbscript, coffeescript, etc. must be preserved
273
            $type = $element->getAttributeNS('','type');
274
            $is_std = preg_match('/^(?:text|application)\/(?:ecma|java)script(\s*;\s*charset\s*=\s*[^;]*)?$/', $type);
275
276
            // Remote scripts should have type specified in HTTP headers.
277
            if ($is_std || $element->getAttributeNS('','src')) {
278
                $element->removeAttributeNS('','type');
279
            }
280
        }
281
    }
282
}
283