htmlparser::scan()   F
last analyzed

Complexity

Conditions 60
Paths 36

Size

Total Lines 145

Duplication

Lines 29
Ratio 20 %

Code Coverage

Tests 70
CRAP Score 519.5163

Importance

Changes 0
Metric Value
cc 60
nc 36
nop 2
dl 29
loc 145
rs 3.3333
c 0
b 0
f 0
ccs 70
cts 141
cp 0.4965
crap 519.5163

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
	define('AR_HTMLPARSER_T_OPEN_TAG', 252);
3
	define('AR_HTMLPARSER_T_CLOSE_TAG', 251);
4
	define('AR_HTMLPARSER_T_TAG_END', 250);
5
	define('AR_HTMLPARSER_T_ASSIGN', 248);
6
	define('AR_HTMLPARSER_T_IDENT', 247);
7
	define('AR_HTMLPARSER_T_STRING', 246);
8
	define('AR_HTMLPARSER_T_ATTRIB', 245);
9
	define('AR_HTMLPARSER_T_NUMBER', 244);
10
	define('AR_HTMLPARSER_T_ATTRIB_VAL', 243);
11
	define('AR_HTMLPARSER_T_DOCTYPE', 242);
12
13
	define('AR_HTMLPARSER_T_EOF', 0);
14
	define('AR_HTMLPARSER_T_TEXT', -1);
15
	define('AR_HTMLPARSER_T_ERROR', -2);
16
17
	define('AR_HTMLPARSER_STATE_TEXT', 1);
18
	define('AR_HTMLPARSER_STATE_OPEN_TAG', 2);
19
	define('AR_HTMLPARSER_STATE_CLOSE_TAG', 3);
20
	define('AR_HTMLPARSER_STATE_ATTRIB', 4);
21
	define('AR_HTMLPARSER_STATE_COMMENT', 5);
22
	define('AR_HTMLPARSER_STATE_SCRIPT', 6);
23
	define('AR_HTMLPARSER_STATE_DOCTYPE', 7);
24
25
	define('CONTEXT_NORMAL', 1);
26
	define('CONTEXT_SCRIPT', 2);
27
28
	class htmlparser {
29
30 16
		protected static function scanner($buffer) {
31 16
			$scanner = array();
32 16
			$scanner['YYBUFFER'] = $buffer."\000";
33 16
			$scanner['YYCURSOR'] = 0;
34 16
			$scanner['YYSTATE'] = AR_HTMLPARSER_STATE_TEXT;
35 16
			$scanner['YYCONTEXT'] = CONTEXT_NORMAL;
36
37 16
			$class_ident_start = array();
38 16
			$start = ord('a');
39 16
			$end   = ord('z');
40 16
			for ($i = $start; $i <= $end; $i++) {
41 16
				$class_ident_start[chr($i)] = chr($i);
42 16
				$class_ident_start[strtoupper(chr($i))] = strtoupper(chr($i));
43 16
			}
44 16
			$scanner['class_ident_start'] = $class_ident_start;
45
46 16
			$class_attrib_start = $class_ident_start;
47 16
			$scanner['class_attrib_start'] = $class_attrib_start;
48
49
50 16
			$class_ident_next = $class_ident_start;
51 16
			$class_number = array();
52 16
			$start = ord('0');
53 16
			$end   = ord('9');
54 16 View Code Duplication
			for ($i = $start; $i <= $end; $i++) {
55 16
				$class_ident_next[chr($i)] = chr($i);
56 16
				$class_number[chr($i)] = chr($i);
57 16
			}
58 16
			$scanner['class_ident_next'] = $class_ident_next;
59 16
			$scanner['class_number'] = $class_number;
60
61
			// List of allowed characters for attribute names;
62 16
			$class_attrib_next = $class_ident_next;
63 16
			$class_attrib_next[':'] = ':';
64 16
			$class_attrib_next['-'] = '-';
65 16
			$class_attrib_next['_'] = '_';
66 16
			$class_attrib_next['.'] = '.';
67
68 16
			$scanner['class_attrib_next'] = $class_attrib_next;
69
70
71 16
			$class_whitespace = array(" " => " ", "\t" => "\t", "\r" => "\r", "\n" => "\n");
72 16
			$scanner['class_whitespace'] = $class_whitespace;
73
74 16
			return $scanner;
75
		}
76
77 16
		protected static function scan(&$scanner, &$value) {
78 16
			$YYCURSOR = &$scanner["YYCURSOR"];
79 16
			$YYBUFFER = &$scanner["YYBUFFER"];
80 16
			$yych = $YYBUFFER[$YYCURSOR];
81 16
			$YYSTATE = &$scanner["YYSTATE"];
82 16
			$YYCONTEXT = &$scanner["YYCONTEXT"];
83
84
			do {
85
				switch (true) {
86 16 View Code Duplication
					case $yych === $scanner['class_attrib_start'][$yych] && ($YYSTATE == AR_HTMLPARSER_STATE_DOCTYPE):
87
						$value = $yych;
88
						while ($scanner['class_attrib_next'][$yych = $YYBUFFER[++$YYCURSOR]] == $yych) {
89
							$value .= $yych;
90
						}
91
						return AR_HTMLPARSER_T_ATTRIB_VAL;
92
					break;
93 16
					case $yych === '"' && ($YYSTATE == AR_HTMLPARSER_STATE_DOCTYPE):
94
						$yych = $yych = $YYBUFFER[++$YYCURSOR];
95 View Code Duplication
						while ($yych !== "\000" && $yych !== '"') {
96
							$value .= $yych;
97
							$yych = $yych = $YYBUFFER[++$YYCURSOR];
98
						}
99
						$value = '"'.$value.'"';
100
						$yych = $YYBUFFER[++$YYCURSOR];
0 ignored issues
show
Unused Code introduced by
$yych is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
101
						return AR_HTMLPARSER_T_ATTRIB_VAL;
102
					break;
103 16
					case $yych === '"' && ($YYSTATE == AR_HTMLPARSER_STATE_ATTRIB):
104 16
					case $yych === "'" && ($YYSTATE == AR_HTMLPARSER_STATE_ATTRIB):
105 3
						$YYSTATE = AR_HTMLPARSER_STATE_OPEN_TAG;
106
107 3
						$quote = $yych;
108 3
						$yych = $yych = $YYBUFFER[++$YYCURSOR];
109 3 View Code Duplication
						while ($yych !== "\000" && $yych !== $quote) {
110 3
							$value .= $yych;
111 3
							$yych = $yych = $YYBUFFER[++$YYCURSOR];
112 3
						}
113 3
						$yych = $YYBUFFER[++$YYCURSOR];
0 ignored issues
show
Unused Code introduced by
$yych is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
114 3
						return AR_HTMLPARSER_T_ATTRIB_VAL;
115
					break;
116 16
					case $yych === '=' && ($YYSTATE == AR_HTMLPARSER_STATE_OPEN_TAG):
117 3
						while ($scanner['class_whitespace'][$yych = $YYBUFFER[++$YYCURSOR]] == $yych);
118 3
						$YYSTATE = AR_HTMLPARSER_STATE_ATTRIB;
119 3
						contine;
120 3
					break;
121 16
					case $yych === $scanner['class_whitespace'][$yych] && (($YYSTATE == AR_HTMLPARSER_STATE_OPEN_TAG) || ($YYSTATE == AR_HTMLPARSER_STATE_CLOSE_TAG) || ($YYSTATE == AR_HTMLPARSER_STATE_DOCTYPE)):
122 3
						$yych = $YYBUFFER[++$YYCURSOR]; continue;
123
					break;
124 16
					case $yych === '-' && ($YYSTATE == AR_HTMLPARSER_STATE_COMMENT):
125
						if (substr($YYBUFFER, $YYCURSOR, 3) == '-->') {
126
							$YYSTATE = AR_HTMLPARSER_STATE_TEXT;
127
							$value = "-->"; $YYCURSOR+=3;
128
							return AR_HTMLPARSER_T_TEXT;
129
						}
130
						$value = '-';
131
						$YYBUFFER[++$YYCURSOR];
132
						return AR_HTMLPARSER_T_TEXT;
133
					break;
134 16
					case ($YYSTATE == AR_HTMLPARSER_STATE_TEXT) && (substr_compare($YYBUFFER, '<!--', $YYCURSOR, 4) == 0 ):
135
							$value		= "<!--"; $YYCURSOR+=4;
136
							$YYSTATE	= AR_HTMLPARSER_STATE_COMMENT;
137
							return AR_HTMLPARSER_T_TEXT;
138
					break;
139 16
					case ($YYSTATE == AR_HTMLPARSER_STATE_SCRIPT) && ( substr_compare($YYBUFFER, '</script>', $YYCURSOR, 9, true) == 0 ):
140
						$YYCONTEXT = CONTEXT_NORMAL;
141
						// fallthrough
142 16
					case ($YYSTATE == AR_HTMLPARSER_STATE_TEXT) && substr($YYBUFFER, $YYCURSOR, 2) == '</':
143 4
						$YYSTATE	= AR_HTMLPARSER_STATE_CLOSE_TAG;
144 4
						$YYCURSOR	+= 1;
145 4
						$value		= "";
146 4
						while ($scanner['class_ident_next'][$yych = $YYBUFFER[++$YYCURSOR]] == $yych) {
147 4
							$value .= $yych;
148 4
						}
149 4
						return AR_HTMLPARSER_T_CLOSE_TAG;
150
					break;
151 16
					case ($YYSTATE == AR_HTMLPARSER_STATE_TEXT) && ( substr_compare($YYBUFFER, '<!doctype', $YYCURSOR, 9 , true) == 0 ):
152
						$YYSTATE	= AR_HTMLPARSER_STATE_DOCTYPE;
153
						$value		= substr($YYBUFFER, $YYCURSOR, 9/* strlen('<!doctype')*/);
154
						$YYCURSOR	+= 9 /*strlen('<!doctype')*/;
155
						return AR_HTMLPARSER_T_DOCTYPE;
156
					break;
157 16
					case $yych == '<' && ($YYSTATE == AR_HTMLPARSER_STATE_TEXT):
158 4
						$YYSTATE	= AR_HTMLPARSER_STATE_OPEN_TAG;
159 4
						$value		= "";
160 4
						while ($scanner['class_ident_next'][$yych = $YYBUFFER[++$YYCURSOR]] == $yych) {
161 4
							$value .= $yych;
162 4
						}
163 4
						if (strtolower($value) == 'script') {
164
							$YYCONTEXT = CONTEXT_SCRIPT;
165
						}
166 4
						return AR_HTMLPARSER_T_OPEN_TAG;
167
					break;
168 16 View Code Duplication
					case $yych === $scanner['class_attrib_start'][$yych] && ($YYSTATE == AR_HTMLPARSER_STATE_OPEN_TAG):
169 3
						$value = $yych;
170 3
						while ($scanner['class_attrib_next'][$yych = $YYBUFFER[++$YYCURSOR]] == $yych) {
171 3
							$value .= $yych;
172 3
						}
173 3
						return AR_HTMLPARSER_T_ATTRIB;
174
					break;
175 16 View Code Duplication
					case $yych === $scanner['class_number'][$yych] && ($YYSTATE == AR_HTMLPARSER_STATE_OPEN_TAG):
176
						$value = $yych;
177
						while ($scanner['class_number'][$yych = $YYBUFFER[++$YYCURSOR]] == $yych) {
178
							$value .= $yych;
179
						}
180
						return AR_HTMLPARSER_T_NUMBER;
181
					break;
182 16
					case $yych === '>' && (($YYSTATE == AR_HTMLPARSER_STATE_OPEN_TAG) || ($YYSTATE == AR_HTMLPARSER_STATE_CLOSE_TAG) || ($YYSTATE == AR_HTMLPARSER_STATE_DOCTYPE)):
183 4
						if ($YYCONTEXT == CONTEXT_SCRIPT) {
184
							$YYSTATE = AR_HTMLPARSER_STATE_SCRIPT;
185
						} else {
186 4
							$YYSTATE = AR_HTMLPARSER_STATE_TEXT;
187
						}
188 4
						$yych = $YYBUFFER[++$YYCURSOR]; continue;
189
					break;
190 16
					case ord($yych) === 0:
191 16
						$value = $yych;
192 16
						return AR_HTMLPARSER_T_EOF;
193
					break;
194 4
					case $yych === $yych && ($YYSTATE == AR_HTMLPARSER_STATE_ATTRIB):
195
						$YYSTATE = AR_HTMLPARSER_STATE_OPEN_TAG;
196
						$value = "";
197
						while ($scanner['class_whitespace'][$yych] !== $yych && ($yych != "\000" && $yych != ">")) {
198
							$value .= $yych;
199
							$yych = $YYBUFFER[++$YYCURSOR];
200
						}
201
						return AR_HTMLPARSER_T_ATTRIB_VAL;
202
					break;
203 4
					case $yych === $yych && ($YYSTATE == AR_HTMLPARSER_STATE_OPEN_TAG):
204
						$yych = $YYBUFFER[++$YYCURSOR]; continue;
205
					break;
206 4
					case ($YYSTATE == AR_HTMLPARSER_STATE_TEXT):
207 4
						$value = "";
208 4
						while ( $yych != '<'  && $yych != "\000" ) {
209 4
							$value .= $yych;
210 4
							$yych = $YYBUFFER[++$YYCURSOR];
211 4
						}
212 4
						return AR_HTMLPARSER_T_TEXT;
213
					break;
214
					default:
215
						$value = $yych;
216
						$YYCURSOR++;
217
						return AR_HTMLPARSER_T_TEXT;
218
				}
219 4
			} while (1);
220
221
		}
222
223 16
		protected static function nextToken(&$parser) {
224 16
			$scanner = &$parser['scanner'];
225
226 16
			$new_token = static::scan($scanner, $new_value);
227
228 16
			if (isset($parser["token_ahead"])) {
229 4
				$parser["token"] = $parser["token_ahead"];
230 4
				$parser["token_value"] = $parser["token_ahead_value"];
231 4
			}
232 16
			$parser["token_ahead"] = $new_token;
233 16
			$parser["token_ahead_value"] = $new_value;
234 16
		}
235
236
237 4
		protected static function parse_Text(&$parser) {
238 4
			$result = "";
239 4
			while ($parser["token_ahead"] == AR_HTMLPARSER_T_TEXT) {
240 4
				static::nextToken($parser);
241 4
				$result .= $parser["token_value"];
242 4
			}
243 4
			return $result;
244
		}
245
246 4
		protected static function parse_Tag_Open(&$parser) {
247
			$singles = array(
248 4
				'br', 'img', 'area', 'link', 'param', 'hr', 'base', 'meta',
249 4
				'input', 'col'
250 4
			);
251
252 4
			$result = array('type' => 'tag');
253 4
			$tagName = $parser["token_ahead_value"];
254 4
			if (in_array(strtolower($tagName), $singles)) {
255
				$result['type'] = 'tagSingle';
256
			}
257 4
			$result['tagName'] = $tagName;
258 4
			static::nextToken($parser);
259 4
			while ($parser["token_ahead"] == AR_HTMLPARSER_T_ATTRIB) {
260 3
				static::nextToken($parser);
261 3
				$attrib = $parser["token_value"];
262 3
				$attrib_value = false;
263 3
				if ($parser["token_ahead"] == AR_HTMLPARSER_T_ATTRIB_VAL) {
264 3
					static::nextToken($parser);
265 3
					$attrib_value = $parser["token_value"];
266 3
				}
267 3
				$result['attribs'][$attrib] = $attrib_value;
268 3
			}
269
270 4
			return $result;
271
		}
272
273 16
		protected static function parse_Node(&$parser, &$stack) {
274 16
			$siblings = array('table', 'tr', 'td', 'li', 'ul');
275
276 16
			if (count($stack)) {
277 4
				$parentNode	= &$stack[count($stack)-1];
278 4
				$tagName	= strtolower($parentNode['tagName']);
279 4
			}
280 16
			$result = array();
281 16
			while ($parser["token_ahead"] != AR_HTMLPARSER_T_EOF) {
282 4
				switch ($parser["token_ahead"]) {
283 4
					case AR_HTMLPARSER_T_TEXT:
284 4
						$node = array('type' => 'text');
285 4
						$node['html'] = static::parse_Text($parser);
286 4
						$result[] = $node;
287 4
					break;
288 4
					case AR_HTMLPARSER_T_DOCTYPE:
289
						$node = array('type' => 'doctype');
290
						static::nextToken($parser);
291
						while ($parser["token_ahead"] == AR_HTMLPARSER_T_ATTRIB_VAL) {
292
							static::nextToken($parser);
293
							$attrib_value = $parser["token_value"];
294
							$node['attribs'][] = $attrib_value;
295
						}
296
						$result[] = $node;
297
					break;
298 4
					case AR_HTMLPARSER_T_OPEN_TAG:
299 4
						$nextTag = strtolower($parser["token_ahead_value"]);
300 4
						if ($nextTag == $tagName && in_array($tagName, $siblings)) {
0 ignored issues
show
Bug introduced by
The variable $tagName does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
301
							if ($parser['options']['noTagResolving']) {
302
								$parentNode['htmlTagClose'] = "";
0 ignored issues
show
Bug introduced by
The variable $parentNode does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
303
							}
304
							return $result;
305
						}
306
307 4
						$node = static::parse_Tag_Open($parser);
308 4
						if ($node) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $node of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
309 4
							if ($node['type'] !== 'tagSingle') {
310 4
								$stack[] = &$node;
311 4
								$node['children'] = static::parse_Node($parser, $stack);
312 4
								array_pop($stack);
313 4
								end($stack);
314 4
							}
315 4
							$result[] = $node;
316 4
						}
317 4
					break;
318 4
					case AR_HTMLPARSER_T_CLOSE_TAG:
319 4
						$closeTag = $parser["token_ahead_value"];
320 4
						if ($tagName != strtolower($closeTag)) {
321
							// continue parsing because closing tag does not match current tag
322
							// FIXME: create a better check
323
							static::nextToken($parser);
324
325
							// if we do not resolve tags, we have to add this one as text
326
							if ($parser['options']['noTagResolving']) {
327
								$node = array('type' => 'text');
328
								$node['html'] = "</".$parser["token_value"].">";
329
								$result[] = $node;
330
							}
331
							continue;
332
						}
333
334
						// if we do not resolve tags, we have to add this one as text
335 4
						if ($parser['options']['noTagResolving']) {
336
							$parentNode['htmlTagClose'] = "</".$parser['token_ahead_value'].">";
337
						}
338
339 4
						static::nextToken($parser);
340 4
						return $result;
341
					default:
342
						static::nextToken($parser);
343 4
				}
344 4
			}
345 16
			if ($parser['options']['noTagResolving']) {
346 12
				$parentNode['htmlTagClose'] = "";
347 12
			}
348 16
			return $result;
349
		}
350
351 4
		protected static function compile_Attribs(&$node) {
352 4
			$result = "";
353 4
			if (is_array($node['attribs'])) {
354 3
				foreach ($node['attribs'] as $key => $value) {
355 3
					$result .= " $key";
356 3
					if ($value !== false) {
357 3
						$result .= "=\"".str_replace('"', '\"', $value)."\"";
358 3
					}
359 3
				}
360 3
			}
361 4
			return $result;
362
		}
363
364 4
		public static function compile($node) {
365 4
			$result = "";
366 4
			switch ($node['type']) {
367 4
				case 'tag':
368 4
				case 'tagSingle':
369 4
					if ($node['tagName']) {
370 4
						$result .= "<".$node['tagName'];
371 4
						$result .= static::compile_Attribs($node);
372 4
						$result .= ">";
373 4
					}
374 4
				case 'root':
375 4
					if (is_array($node['children'])) {
376 4
						foreach ($node['children'] as $child) {
377 4
							$result .= static::compile($child);
378 4
						}
379 4
					}
380 4
					if ($node['type'] == 'tag') {
381 4
						if (isset($node['htmlTagClose'])) {
382
							$result .= $node['htmlTagClose'];
383
						} else {
384 4
							$result .= "</".$node['tagName'].">";
385
						}
386 4
					}
387 4
				break;
388 4
				case 'doctype':
389
					$result .= "<!DOCTYPE";
390
					foreach ($node['attribs'] as $attrib) {
391
						$result .= " $attrib";
392
					}
393
					$result .= ">";
394
				break;
395 4
				default:
396 4
					$result .= $node['html'];
397 4
				break;
398 4
			}
399 4
			return $result;
400
		}
401
402 16
		public static function parse($document, $options = false) {
403 16
			if (!$options) {
404 4
				$options = array();
405 4
			}
406 16
			$parser = array('options' => $options);
407 16
			$scanner = static::scanner($document);
408 16
			$parser['scanner'] = &$scanner;
409 16
			$stack = array();
410 16
			static::nextToken($parser);
411
			$result = array(
412 16
				'type' => 'root',
413 16
				'children' => static::parse_Node($parser, $stack)
414 16
			);
415 16
			return $result;
416
		}
417
	}
418