source: trunk/htdocs/system/classes/htmltokenset.php @ 4832

Revision 4832, 6.4 KB checked in by lildude, 12 months ago (diff)

Coding standards

  • 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 ? Utils::htmlspecialchars( html_entity_decode( $token['value'], ENT_QUOTES, 'UTF-8' ) ) : $token['value'];
38                                break;
39
40                        case HTMLTokenizer::NODE_TYPE_ELEMENT_OPEN:
41                        case HTMLTokenizer::NODE_TYPE_ELEMENT_EMPTY:
42                                $out  = '<' . $token['name'];
43                                if ( isset( $token['attrs'] ) && is_array( $token['attrs'] ) ) {
44                                        foreach ( $token['attrs'] as $attr => $attrval ) {
45                                                $out .= " {$attr}=\"";
46                                                if ( $escape ) {
47                                                        $out .= Utils::htmlspecialchars( html_entity_decode( $attrval, ENT_QUOTES, 'UTF-8' ) );
48                                                }
49                                                else {
50                                                        $out .= html_entity_decode( $attrval, ENT_QUOTES, 'UTF-8' );
51                                                }
52                                                $out .= '"';
53                                        }
54                                }
55                                $out .= '>';
56                                break;
57
58                        case HTMLTokenizer::NODE_TYPE_ELEMENT_CLOSE:
59                                $out = "</{$token['name']}>";
60                                break;
61
62                        case HTMLTokenizer::NODE_TYPE_PI:
63                                $out = "<?{$token['name']}{$token['value']}>";
64                                break;
65
66                        case HTMLTokenizer::NODE_TYPE_COMMENT:
67                                $out = "<!--{$token['value']}-->";
68                                break;
69
70                        case HTMLTokenizer::NODE_TYPE_CDATA_SECTION:
71                                $out = "<![CDATA[{$token['value']}]]>";
72                                break;
73
74                        case HTMLTokenizer::NODE_TYPE_STATEMENT:
75                                $out = "<!{$token['name']}";
76                                if ( !empty($token['value']) ) {
77                                        $out .= " {$token['value']}";
78                                }
79                                $out .= ">";
80                                break;
81                }
82                return $out;
83        }
84
85        public function get_end_offset()
86        {
87                return $this->sliceOffsetBegin + $this->sliceOffsetLength;
88        }
89
90        /**
91         * Fetch a section of the tokens, based on passed criteria
92         */
93        public function slice( $names, array $attr = null )
94        {
95                $names = (array)$names;
96                $ret = array();
97                foreach ( $names as $name ) {
98                        $offset = 0;
99                        $slices = array();
100                        while ( $slice = $this->find_slice( $offset, $name, $attr ) ) {
101                                $slices[] = $slice;
102                                $offset = $slice->get_end_offset();
103                        }
104                        // Meed to reverse this because we need to splice the last chunks first
105                        // if we splice the earlier chunks first, then the offsets get all
106                        // messed up. Trust me.
107                        $ret = array_merge( $ret, array_reverse( $slices ) );
108                }
109                return $ret;
110        }
111
112        protected function find_slice( $offset, $name, array $attr )
113        {
114                // find start:
115                $foundStart = false;
116                for ( ; $offset < count( $this->tokens ); $offset++ ) {
117                        // short circuit if possible
118                        if ( $this->tokens[$offset]['type'] != HTMLTokenizer::NODE_TYPE_ELEMENT_OPEN ) {
119                                continue;
120                        }
121                        if ( $this->tokens[$offset]['name'] != $name ) {
122                                continue;
123                        }
124
125                        // check attributes
126                        if ( !count( $attr ) ) {
127                                $foundStart = true;
128                                break; // To: FOUNDSTARTBREAKPOINT
129                        }
130                        foreach ( $attr as $compareName => $compareVal ) {
131                                if ( isset( $this->tokens[$offset]['attrs'][$compareName] ) &&
132                                                stripos( $this->tokens[$offset]['attrs'][$compareName], $compareVal ) !== false ) {
133                                        $foundStart = true;
134                                        break 2; // To: FOUNDSTARTBREAKPOINT
135                                }
136                        }
137                }
138                // Fake label: FOUNDSTARTBREAKPOINT
139
140                // short circuit if possible:
141                if ( !$foundStart ) {
142                        return false;
143                }
144
145                $startOffset = $offset;
146
147                // find the closing tag
148                // (keep a stack so we don't mistake a nested node for this closing node)
149                $stackDepth = 0;
150                $foundEnd = false;
151                for ( ; $offset < count( $this->tokens ); $offset++ ) {
152                        switch ( $this->tokens[$offset]['type'] ) {
153                                case HTMLTokenizer::NODE_TYPE_ELEMENT_OPEN:
154                                        if ( $this->tokens[$offset]['name'] == $name ) {
155                                                ++$stackDepth;
156                                        }
157                                        break;
158                                case HTMLTokenizer::NODE_TYPE_ELEMENT_CLOSE:
159                                        if ( $this->tokens[$offset]['name'] == $name ) {
160                                                --$stackDepth;
161                                        }
162                                        break;
163                                // default: skip
164                        }
165                        if ( $stackDepth <= 0 ) {
166                                $foundEnd = true;
167                                break;
168                        }
169                }
170
171                // short circuit if possible:
172                if ( !$foundEnd ) {
173                        return false;
174                }
175
176                $offsetLength = $offset - $startOffset + 1;
177
178                // now, place the found set into a new HTMLTokenSet:
179                $slice = new HTMLTokenSet($this->escape);
180                $slice->sliceOffsetBegin  = $startOffset;
181                $slice->sliceOffsetLength = $offsetLength;
182                $slice->tokens = array_slice( $this->tokens, $slice->sliceOffsetBegin, $slice->sliceOffsetLength );
183                return $slice;
184        }
185
186        public function trim_container()
187        {
188                $this->tokens = array_slice( $this->tokens, 1, -1 );
189        }
190
191        public function replace_slice( HTMLTokenSet $slice )
192        {
193                array_splice(
194                        $this->tokens,
195                        $slice->sliceOffsetBegin,
196                        $slice->sliceOffsetLength,
197                        $slice->tokens
198                );
199        }
200
201        public function tokenize_replace( $source )
202        {
203                $ht = new HTMLTokenizer( $source, $this->escape );
204                $this->tokens = $ht->parse()->tokens;
205                return $this->tokens;
206        }
207
208        /**
209         * Insert an HTMLTokenset before the given position
210         * @param HTMLTokenset $set. The HTMLTokenset to insert
211         * @param <type> $pos. The position to insert the HTMLTokenset before
212         * @return Nothing
213         */
214        public function insert( HTMLTokenset $set, $pos = 0 )
215        {
216                $set->end();
217                $length = $set->key() - 1;
218
219                $pre = array_slice( $this->tokens, 0, $pos );
220                $post = array_slice( $this->tokens, $pos );
221                $set->rewind();
222                while ( $set->valid() ) {
223                        $pre[] = $set->current();
224                        $set->next();
225                }
226                $this->tokens = array_merge( $pre, $post );
227        }
228
229        ////////////////////////////////////////////////////
230
231        // Iterator implemetation:
232
233        public function rewind()
234        {
235                reset( $this->tokens );
236        }
237
238        public function current()
239        {
240                return current( $this->tokens );
241        }
242
243        public function key()
244        {
245                return key( $this->tokens );
246        }
247
248        public function next()
249        {
250                return next( $this->tokens );
251        }
252
253        public function valid()
254        {
255                return $this->current() !== false;
256        }
257
258        public function end()
259        {
260                return end( $this->tokens );
261        }
262
263        // ArrayAccess implementation
264
265        public function offsetExists( $offset )
266        {
267                return isset( $this->tokens[ $offset ] );
268        }
269
270        public function offsetGet( $offset )
271        {
272                return $this->tokens[ $offset ];
273        }
274
275        public function offsetSet( $offset, $value )
276        {
277                if ( $offset === null ) {
278                        $this->tokens[] = $value;
279                }
280                else {
281                        $this->tokens[ $offset ] = $value;
282                }
283        }
284
285        public function offsetUnset( $offset )
286        {
287                unset( $this->tokens[ $offset ] );
288        }
289}
290?>
Note: See TracBrowser for help on using the repository browser.