1<?php
2
3class HTMLPurifier_Printer_HTMLDefinition extends HTMLPurifier_Printer
4{
5
6    /**
7     * @type HTMLPurifier_HTMLDefinition, for easy access
8     */
9    protected $def;
10
11    /**
12     * @param HTMLPurifier_Config $config
13     * @return string
14     */
15    public function render($config)
16    {
17        $ret = '';
18        $this->config =& $config;
19
20        $this->def = $config->getHTMLDefinition();
21
22        $ret .= $this->start('div', array('class' => 'HTMLPurifier_Printer'));
23
24        $ret .= $this->renderDoctype();
25        $ret .= $this->renderEnvironment();
26        $ret .= $this->renderContentSets();
27        $ret .= $this->renderInfo();
28
29        $ret .= $this->end('div');
30
31        return $ret;
32    }
33
34    /**
35     * Renders the Doctype table
36     * @return string
37     */
38    protected function renderDoctype()
39    {
40        $doctype = $this->def->doctype;
41        $ret = '';
42        $ret .= $this->start('table');
43        $ret .= $this->element('caption', 'Doctype');
44        $ret .= $this->row('Name', $doctype->name);
45        $ret .= $this->row('XML', $doctype->xml ? 'Yes' : 'No');
46        $ret .= $this->row('Default Modules', implode(', ', $doctype->modules));
47        $ret .= $this->row('Default Tidy Modules', implode(', ', $doctype->tidyModules));
48        $ret .= $this->end('table');
49        return $ret;
50    }
51
52
53    /**
54     * Renders environment table, which is miscellaneous info
55     * @return string
56     */
57    protected function renderEnvironment()
58    {
59        $def = $this->def;
60
61        $ret = '';
62
63        $ret .= $this->start('table');
64        $ret .= $this->element('caption', 'Environment');
65
66        $ret .= $this->row('Parent of fragment', $def->info_parent);
67        $ret .= $this->renderChildren($def->info_parent_def->child);
68        $ret .= $this->row('Block wrap name', $def->info_block_wrapper);
69
70        $ret .= $this->start('tr');
71        $ret .= $this->element('th', 'Global attributes');
72        $ret .= $this->element('td', $this->listifyAttr($def->info_global_attr), null, 0);
73        $ret .= $this->end('tr');
74
75        $ret .= $this->start('tr');
76        $ret .= $this->element('th', 'Tag transforms');
77        $list = array();
78        foreach ($def->info_tag_transform as $old => $new) {
79            $new = $this->getClass($new, 'TagTransform_');
80            $list[] = "<$old> with $new";
81        }
82        $ret .= $this->element('td', $this->listify($list));
83        $ret .= $this->end('tr');
84
85        $ret .= $this->start('tr');
86        $ret .= $this->element('th', 'Pre-AttrTransform');
87        $ret .= $this->element('td', $this->listifyObjectList($def->info_attr_transform_pre));
88        $ret .= $this->end('tr');
89
90        $ret .= $this->start('tr');
91        $ret .= $this->element('th', 'Post-AttrTransform');
92        $ret .= $this->element('td', $this->listifyObjectList($def->info_attr_transform_post));
93        $ret .= $this->end('tr');
94
95        $ret .= $this->end('table');
96        return $ret;
97    }
98
99    /**
100     * Renders the Content Sets table
101     * @return string
102     */
103    protected function renderContentSets()
104    {
105        $ret = '';
106        $ret .= $this->start('table');
107        $ret .= $this->element('caption', 'Content Sets');
108        foreach ($this->def->info_content_sets as $name => $lookup) {
109            $ret .= $this->heavyHeader($name);
110            $ret .= $this->start('tr');
111            $ret .= $this->element('td', $this->listifyTagLookup($lookup));
112            $ret .= $this->end('tr');
113        }
114        $ret .= $this->end('table');
115        return $ret;
116    }
117
118    /**
119     * Renders the Elements ($info) table
120     * @return string
121     */
122    protected function renderInfo()
123    {
124        $ret = '';
125        $ret .= $this->start('table');
126        $ret .= $this->element('caption', 'Elements ($info)');
127        ksort($this->def->info);
128        $ret .= $this->heavyHeader('Allowed tags', 2);
129        $ret .= $this->start('tr');
130        $ret .= $this->element('td', $this->listifyTagLookup($this->def->info), array('colspan' => 2));
131        $ret .= $this->end('tr');
132        foreach ($this->def->info as $name => $def) {
133            $ret .= $this->start('tr');
134            $ret .= $this->element('th', "<$name>", array('class' => 'heavy', 'colspan' => 2));
135            $ret .= $this->end('tr');
136            $ret .= $this->start('tr');
137            $ret .= $this->element('th', 'Inline content');
138            $ret .= $this->element('td', $def->descendants_are_inline ? 'Yes' : 'No');
139            $ret .= $this->end('tr');
140            if (!empty($def->excludes)) {
141                $ret .= $this->start('tr');
142                $ret .= $this->element('th', 'Excludes');
143                $ret .= $this->element('td', $this->listifyTagLookup($def->excludes));
144                $ret .= $this->end('tr');
145            }
146            if (!empty($def->attr_transform_pre)) {
147                $ret .= $this->start('tr');
148                $ret .= $this->element('th', 'Pre-AttrTransform');
149                $ret .= $this->element('td', $this->listifyObjectList($def->attr_transform_pre));
150                $ret .= $this->end('tr');
151            }
152            if (!empty($def->attr_transform_post)) {
153                $ret .= $this->start('tr');
154                $ret .= $this->element('th', 'Post-AttrTransform');
155                $ret .= $this->element('td', $this->listifyObjectList($def->attr_transform_post));
156                $ret .= $this->end('tr');
157            }
158            if (!empty($def->auto_close)) {
159                $ret .= $this->start('tr');
160                $ret .= $this->element('th', 'Auto closed by');
161                $ret .= $this->element('td', $this->listifyTagLookup($def->auto_close));
162                $ret .= $this->end('tr');
163            }
164            $ret .= $this->start('tr');
165            $ret .= $this->element('th', 'Allowed attributes');
166            $ret .= $this->element('td', $this->listifyAttr($def->attr), array(), 0);
167            $ret .= $this->end('tr');
168
169            if (!empty($def->required_attr)) {
170                $ret .= $this->row('Required attributes', $this->listify($def->required_attr));
171            }
172
173            $ret .= $this->renderChildren($def->child);
174        }
175        $ret .= $this->end('table');
176        return $ret;
177    }
178
179    /**
180     * Renders a row describing the allowed children of an element
181     * @param HTMLPurifier_ChildDef $def HTMLPurifier_ChildDef of pertinent element
182     * @return string
183     */
184    protected function renderChildren($def)
185    {
186        $context = new HTMLPurifier_Context();
187        $ret = '';
188        $ret .= $this->start('tr');
189        $elements = array();
190        $attr = array();
191        if (isset($def->elements)) {
192            if ($def->type == 'strictblockquote') {
193                $def->validateChildren(array(), $this->config, $context);
194            }
195            $elements = $def->elements;
196        }
197        if ($def->type == 'chameleon') {
198            $attr['rowspan'] = 2;
199        } elseif ($def->type == 'empty') {
200            $elements = array();
201        } elseif ($def->type == 'table') {
202            $elements = array_flip(
203                array(
204                    'col',
205                    'caption',
206                    'colgroup',
207                    'thead',
208                    'tfoot',
209                    'tbody',
210                    'tr'
211                )
212            );
213        }
214        $ret .= $this->element('th', 'Allowed children', $attr);
215
216        if ($def->type == 'chameleon') {
217
218            $ret .= $this->element(
219                'td',
220                '<em>Block</em>: ' .
221                $this->escape($this->listifyTagLookup($def->block->elements)),
222                null,
223                0
224            );
225            $ret .= $this->end('tr');
226            $ret .= $this->start('tr');
227            $ret .= $this->element(
228                'td',
229                '<em>Inline</em>: ' .
230                $this->escape($this->listifyTagLookup($def->inline->elements)),
231                null,
232                0
233            );
234
235        } elseif ($def->type == 'custom') {
236
237            $ret .= $this->element(
238                'td',
239                '<em>' . ucfirst($def->type) . '</em>: ' .
240                $def->dtd_regex
241            );
242
243        } else {
244            $ret .= $this->element(
245                'td',
246                '<em>' . ucfirst($def->type) . '</em>: ' .
247                $this->escape($this->listifyTagLookup($elements)),
248                null,
249                0
250            );
251        }
252        $ret .= $this->end('tr');
253        return $ret;
254    }
255
256    /**
257     * Listifies a tag lookup table.
258     * @param array $array Tag lookup array in form of array('tagname' => true)
259     * @return string
260     */
261    protected function listifyTagLookup($array)
262    {
263        ksort($array);
264        $list = array();
265        foreach ($array as $name => $discard) {
266            if ($name !== '#PCDATA' && !isset($this->def->info[$name])) {
267                continue;
268            }
269            $list[] = $name;
270        }
271        return $this->listify($list);
272    }
273
274    /**
275     * Listifies a list of objects by retrieving class names and internal state
276     * @param array $array List of objects
277     * @return string
278     * @todo Also add information about internal state
279     */
280    protected function listifyObjectList($array)
281    {
282        ksort($array);
283        $list = array();
284        foreach ($array as $obj) {
285            $list[] = $this->getClass($obj, 'AttrTransform_');
286        }
287        return $this->listify($list);
288    }
289
290    /**
291     * Listifies a hash of attributes to AttrDef classes
292     * @param array $array Array hash in form of array('attrname' => HTMLPurifier_AttrDef)
293     * @return string
294     */
295    protected function listifyAttr($array)
296    {
297        ksort($array);
298        $list = array();
299        foreach ($array as $name => $obj) {
300            if ($obj === false) {
301                continue;
302            }
303            $list[] = "$name&nbsp;=&nbsp;<i>" . $this->getClass($obj, 'AttrDef_') . '</i>';
304        }
305        return $this->listify($list);
306    }
307
308    /**
309     * Creates a heavy header row
310     * @param string $text
311     * @param int $num
312     * @return string
313     */
314    protected function heavyHeader($text, $num = 1)
315    {
316        $ret = '';
317        $ret .= $this->start('tr');
318        $ret .= $this->element('th', $text, array('colspan' => $num, 'class' => 'heavy'));
319        $ret .= $this->end('tr');
320        return $ret;
321    }
322}
323
324// vim: et sw=4 sts=4
325