root/trunk/htdocs/system/classes/htmltokenset.php

Revision 3619, 5.7 kB (checked in by moeffju, 4 weeks ago)

* Fix PI parsing in HTMLTokenizer

* Fix STATEMENT serialization in HTMLTokenSet

  • Property svn:eol-style set to native
Line 
1<?php
2/**
3 * @package Habari
4 *
5 */
6
7/**
8 * HTML Token Set (created by @see HTMLTokenizer)
9 */
10class HTMLTokenSet implements Iterator, ArrayAccess
11{
12    protected $tokens = array();
13
14    protected $sliceOffsetBegin  = null;
15    protected $sliceOffsetLength = null;
16
17    public $escape;
18
19    public function __construct($escape = true)
20    {
21        $this->escape = $escape;
22    }
23
24    public function __tostring()
25    {
26        $out = '';
27        foreach ( $this->tokens as $token ) {
28            $out .= self::token_to_string($token, $this->escape);
29        }
30        return $out;
31    }
32
33    public static function token_to_string( array $token, $escape = true )
34    {
35        switch ($token['type']) {
36            case HTMLTokenizer::NODE_TYPE_TEXT:
37                return $escape ? htmlspecialchars($token['value']) : $token['value'];
38                break;
39
40            case HTMLTokenizer::NODE_TYPE_ELEMENT_OPEN:
41                $out  = '<' . $token['name'];
42                if ( isset( $token['attrs'] ) && is_array( $token['attrs'] ) ) {
43                    foreach ( $token['attrs'] as $attr => $attrval ) {
44                        $out .= " {$attr}=\"";
45                        if ($escape) {
46                            $out .= htmlspecialchars( html_entity_decode( $attrval, ENT_QUOTES, 'utf-8' ), ENT_COMPAT, 'utf-8' );
47                        } else {
48                            $out .= html_entity_decode( $attrval, ENT_QUOTES, 'utf-8' );
49                        }
50                        $out .= '"';
51                    }
52                }
53                $out .= '>';
54                break;
55
56            case HTMLTokenizer::NODE_TYPE_ELEMENT_CLOSE:
57                $out = "</{$token['name']}>";
58                break;
59
60            case HTMLTokenizer::NODE_TYPE_PI:
61                $out = "<?{$token['name']}{$token['value']}>";
62                break;
63
64            case HTMLTokenizer::NODE_TYPE_COMMENT:
65                $out = "<!--{$token['value']}-->";
66                break;
67
68            case HTMLTokenizer::NODE_TYPE_CDATA_SECTION:
69                $out = "<![CDATA[{$token['value']}]]>";
70                break;
71
72            case HTMLTokenizer::NODE_TYPE_STATEMENT:
73                $out = "<!{$token['name']}";
74                if (!empty($token['value'])) {
75                    $out .= " {$token['value']}";
76                }
77                $out .= ">";
78                break;
79        }
80        return $out;
81    }
82
83    public function get_end_offset()
84    {
85        return $this->sliceOffsetBegin + $this->sliceOffsetLength;
86    }
87
88    /**
89     * Fetch a section of the tokens, based on passed criteria
90     */
91    public function slice( $names, array $attr = null )
92    {
93        $names = (array)$names;
94        $ret = array();
95        foreach ($names as $name) {
96            $offset = 0;
97            $slices = array();
98            while ( $slice = $this->find_slice( $offset, $name, $attr ) ) {
99                $slices[] = $slice;
100                $offset = $slice->get_end_offset();
101            }
102            // Meed to reverse this because we need to splice the last chunks first
103            // if we splice the earlier chunks first, then the offsets get all
104            // messed up. Trust me.
105            $ret = array_merge($ret, array_reverse( $slices ));
106        }
107        return $ret;
108    }
109
110    protected function find_slice( $offset, $name, array $attr)
111    {
112        // find start:
113        $foundStart = false;
114        for ( ; $offset < count($this->tokens); $offset++) {
115            // short circuit if possible
116            if ( $this->tokens[$offset]['type'] != HTMLTokenizer::NODE_TYPE_ELEMENT_OPEN ) {
117                continue;
118            }
119            if ( $this->tokens[$offset]['name'] != $name ) {
120                continue;
121            }
122
123            // check attributes
124            if ( !count($attr) ) {
125                $foundStart = true;
126                break; // To: FOUNDSTARTBREAKPOINT
127            }
128            foreach ( $attr as $compareName => $compareVal ) {
129                if ( isset( $this->tokens[$offset]['attrs'][$compareName] ) &&
130                        stripos( $this->tokens[$offset]['attrs'][$compareName], $compareVal ) !== false ) {
131                    $foundStart = true;
132                    break 2; // To: FOUNDSTARTBREAKPOINT
133                }
134            }
135        }
136        // Fake label: FOUNDSTARTBREAKPOINT
137
138        // short circuit if possible:
139        if ( !$foundStart ) {
140            return false;
141        }
142
143        $startOffset = $offset;
144
145        // find the closing tag
146        // (keep a stack so we don't mistake a nested node for this closing node)
147        $stackDepth = 0;
148        $foundEnd = false;
149        for ( ; $offset < count( $this->tokens ) ; $offset++ ) {
150            switch ( $this->tokens[$offset]['type'] ) {
151                case HTMLTokenizer::NODE_TYPE_ELEMENT_OPEN:
152                    if ( $this->tokens[$offset]['name'] == $name ) {
153                        ++$stackDepth;
154                    }
155                    break;
156                case HTMLTokenizer::NODE_TYPE_ELEMENT_CLOSE:
157                    if ( $this->tokens[$offset]['name'] == $name ) {
158                        --$stackDepth;
159                    }
160                    break;
161                // default: skip
162            }
163            if ( $stackDepth <= 0 ) {
164                $foundEnd = true;
165                break;
166            }
167        }
168
169        // short circuit if possible:
170        if ( !$foundEnd ) {
171            return false;
172        }
173
174        $offsetLength = $offset - $startOffset + 1;
175
176        // now, place the found set into a new HTMLTokenSet:
177        $slice = new HTMLTokenSet($this->escape);
178        $slice->sliceOffsetBegin  = $startOffset;
179        $slice->sliceOffsetLength = $offsetLength;
180        $slice->tokens = array_slice($this->tokens, $slice->sliceOffsetBegin, $slice->sliceOffsetLength);
181        return $slice;
182    }
183
184    public function trim_container()
185    {
186        $this->tokens = array_slice($this->tokens, 1, -1);
187    }
188
189    public function replace_slice( HTMLTokenSet $slice )
190    {
191        array_splice(
192            $this->tokens,
193            $slice->sliceOffsetBegin,
194            $slice->sliceOffsetLength,
195            $slice->tokens
196        );
197    }
198
199    public function tokenize_replace( $source )
200    {
201        $ht = new HTMLTokenizer( $source, $this->escape );
202        $this->tokens = $ht->parse()->tokens;
203        return $this->tokens;
204    }
205
206    ////////////////////////////////////////////////////
207
208    // Iterator implemetation:
209
210    public function rewind()
211    {
212        reset( $this->tokens );
213    }
214
215    public function current()
216    {
217        return current( $this->tokens );
218    }
219
220    public function key()
221    {
222        return key( $this->tokens );
223    }
224
225    public function next()
226    {
227        return next( $this->tokens );
228    }
229
230    public function valid()
231    {
232        return $this->current() !== false;
233    }
234
235    // ArrayAccess implementation
236
237    public function offsetExists( $offset )
238    {
239        return isset( $this->tokens[ $offset ] );
240    }
241
242    public function offsetGet( $offset )
243    {
244        return $this->tokens[ $offset ];
245    }
246
247    public function offsetSet( $offset, $value )
248    {
249        if ( $offset === null) {
250            $this->tokens[] = $value;
251        } else {
252            $this->tokens[ $offset ] = $value;
253        }
254    }
255
256    public function offsetUnset( $offset )
257    {
258        unset( $this->tokens[ $offset ] );
259    }
260}
261?>
Note: See TracBrowser for help on using the browser.