1<?php
2
3class HTMLPurifier_Injector_RemoveEmpty extends HTMLPurifier_Injector
4{
5    /**
6     * @type HTMLPurifier_Context
7     */
8    private $context;
9
10    /**
11     * @type HTMLPurifier_Config
12     */
13    private $config;
14
15    /**
16     * @type HTMLPurifier_AttrValidator
17     */
18    private $attrValidator;
19
20    /**
21     * @type bool
22     */
23    private $removeNbsp;
24
25    /**
26     * @type bool
27     */
28    private $removeNbspExceptions;
29
30    /**
31     * Cached contents of %AutoFormat.RemoveEmpty.Predicate
32     * @type array
33     */
34    private $exclude;
35
36    /**
37     * @param HTMLPurifier_Config $config
38     * @param HTMLPurifier_Context $context
39     * @return void
40     */
41    public function prepare($config, $context)
42    {
43        parent::prepare($config, $context);
44        $this->config = $config;
45        $this->context = $context;
46        $this->removeNbsp = $config->get('AutoFormat.RemoveEmpty.RemoveNbsp');
47        $this->removeNbspExceptions = $config->get('AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions');
48        $this->exclude = $config->get('AutoFormat.RemoveEmpty.Predicate');
49        foreach ($this->exclude as $key => $attrs) {
50            if (!is_array($attrs)) {
51                // HACK, see HTMLPurifier/Printer/ConfigForm.php
52                $this->exclude[$key] = explode(';', $attrs);
53            }
54        }
55        $this->attrValidator = new HTMLPurifier_AttrValidator();
56    }
57
58    /**
59     * @param HTMLPurifier_Token $token
60     */
61    public function handleElement(&$token)
62    {
63        if (!$token instanceof HTMLPurifier_Token_Start) {
64            return;
65        }
66        $next = false;
67        $deleted = 1; // the current tag
68        for ($i = count($this->inputZipper->back) - 1; $i >= 0; $i--, $deleted++) {
69            $next = $this->inputZipper->back[$i];
70            if ($next instanceof HTMLPurifier_Token_Text) {
71                if ($next->is_whitespace) {
72                    continue;
73                }
74                if ($this->removeNbsp && !isset($this->removeNbspExceptions[$token->name])) {
75                    $plain = str_replace("\xC2\xA0", "", $next->data);
76                    $isWsOrNbsp = $plain === '' || ctype_space($plain);
77                    if ($isWsOrNbsp) {
78                        continue;
79                    }
80                }
81            }
82            break;
83        }
84        if (!$next || ($next instanceof HTMLPurifier_Token_End && $next->name == $token->name)) {
85            $this->attrValidator->validateToken($token, $this->config, $this->context);
86            $token->armor['ValidateAttributes'] = true;
87            if (isset($this->exclude[$token->name])) {
88                $r = true;
89                foreach ($this->exclude[$token->name] as $elem) {
90                    if (!isset($token->attr[$elem])) $r = false;
91                }
92                if ($r) return;
93            }
94            if (isset($token->attr['id']) || isset($token->attr['name'])) {
95                return;
96            }
97            $token = $deleted + 1;
98            for ($b = 0, $c = count($this->inputZipper->front); $b < $c; $b++) {
99                $prev = $this->inputZipper->front[$b];
100                if ($prev instanceof HTMLPurifier_Token_Text && $prev->is_whitespace) {
101                    continue;
102                }
103                break;
104            }
105            // This is safe because we removed the token that triggered this.
106            $this->rewindOffset($b+$deleted);
107            return;
108        }
109    }
110}
111
112// vim: et sw=4 sts=4
113