PK e™fM€’¥å å maintenance/scrape-ncr.phpnu W+A„¶ \s*([^<]+?)\s*\s*\s*
*/ $this->ignore_lf_token = 2; $this->original_mode = $this->mode; $this->flag_frameset_ok = false; $this->mode = self::IN_CDATA_RCDATA; /* Switch the tokeniser's content model flag to the RCDATA state. */ $this->content_model = Tokenizer::RCDATA; break; /* A start tag token whose tag name is "xmp" */ case 'xmp': /* Reconstruct the active formatting elements, if any. */ $this->reconstructActiveFormattingElements(); $this->flag_frameset_ok = false; $this->insertCDATAElement($token); break; case 'iframe': $this->flag_frameset_ok = false; $this->insertCDATAElement($token); break; case 'noembed': case 'noscript': // XSCRIPT: should check scripting flag $this->insertCDATAElement($token); break; /* A start tag whose tag name is "select" */ case 'select': /* Reconstruct the active formatting elements, if any. */ $this->reconstructActiveFormattingElements(); /* Insert an HTML element for the token. */ $this->insertElement($token); $this->flag_frameset_ok = false; /* If the insertion mode is one of in table", "in caption", * "in column group", "in table body", "in row", or "in * cell", then switch the insertion mode to "in select in * table". Otherwise, switch the insertion mode to "in * select". */ if ( $this->mode === self::IN_TABLE || $this->mode === self::IN_CAPTION || $this->mode === self::IN_COLUMN_GROUP || $this->mode ==+self::IN_TABLE_BODY || $this->mode === self::IN_ROW || $this->mode === self::IN_CELL ) { $this->mode = self::IN_SELECT_IN_TABLE; } else { $this->mode = self::IN_SELECT; } break; case 'option': case 'optgroup': if ($this->elementInScope('option')) { $this->emitToken(array( 'name' => 'option', 'type' => Tokenizer::ENDTAG, )); } $this->reconstructActiveFormattingElements(); $this->insertElement($token); break; case 'rp': case 'rt': /* If the stack of open elements has a ruby element in scope, then generate * implied end tags. If the current node is not then a ruby element, this is * a parse error; pop all the nodes from the current node up to the node * immediately before the bottommost ruby element on the stack of open elements. */ if ($this->elementInScope('ruby')) { $this->generateImpliedEndTags(); } $peek = false; do { if ($peek) { // parse error } $peek = array_pop($this->stack); } while ($peek->tagName !== 'ruby'); $this->stack[] = $peek; // we popped one too many $this->insertElement($token); break; // spec diversion case 'math': $this->reconstructActiveFormattingElements(); $token = $this->adjustMathMLAttributes($token); $token = $this->adjustForeignAttributes($token); $this->insertForeignElement($token, self::NS_MATHML); if (isset($token['self-closing'])) { // XERROR: acknowledge the token's self-closing flag array_pop($this->stack); } if ($this->mode !== self::IN_FOREIGN_CONTENT) { $this->secondary_mode = $this->mode; $this->mode = self::IN_FOREIGN_CONTENT; } break; case 'svg': $this->reconstructActiveFormattingElements(); $token = $this->adjustSVGAttributes($token); $token = $this->adjustForeignAttributes($token); $this->insertForeignElement($token, self::NS_SVG); if (isset($token['self-closing'])) { // XERROR: acknowledge the token's self-closing flag array_pop($this->stack); } if ($this->mode !== self::IN_FOREIGN_CONTENT) { $this->secondary_mode = $this->mode; $this->mode = self::IN_FOREIGN_CONTENT; } break; case 'caption': case 'col': case 'colgroup': case 'frame': case 'head': case 'tbody': case 'td': case 'tfoot': case 'th': case 'thead': case 'tr': // parse error break; /* A start tag token not covered by the previous entries */ default: /* Reconstruct the active formatting elements, if any. */ $this->reconstructActiveFormattingElements(); $this->insertElement($token); /* This element will be a phrasing element. */ break; } break; case Tokenizer::ENDTAG: switch($token['name']) { /* An end tag with the tag name "body" */ case 'body': /* If the second element in the stack of open elements is not a body element, this is a parse error. Ignore the token. (innerHTML case) */ if(count($this->stack) < 2 || $this->stack[1]->tagName !== 'body') { $this->ignored = true; /* Otherwise, if there is a node in the stack of open * elements that is not either a dd element, a dt * element, an li element, an optgroup element, an * option element, a p element, an rp element, an rt * element, a tbody element, a td element, a tfoot * element, a th element, a thead element, a tr element, * the body element, or the html element, then this is a * parse error. */ } else { // XERROR: implement this check for parse error } /* Change the insertion mode to "after body". */ $this->mode = self::AFTER_BODY; break; /* An end tag with the tag name "html" */ case 'html': /* Act as if an end tag with tag name "body" had been seen, then, if that token wasn't ignored, reprocess the current token. */ $this->emitToken(array( 'name' => 'body', 'type' => Tokenizer::ENDTAG )); if (!$this->ignored) $this->emitToken($token); break; case 'address': case 'article': case 'aside': case 'blockquote': case 'center': case 'datagrid': case 'details': case 'dir': case 'div': case 'dl': case 'fieldset': case 'figure': case 'footer': case 'header': case 'hgroup': case 'listing': case 'menu': case 'nav': case 'ol': case 'pre': case 'section': case 'ul': /* If the stack of open elements has an element in scope with the same tag name as that of the token, then generate implied end tags. */ if($this->elementInScope($token['name'])) { $this->generateImpliedEndTags(); /* Now, if the current node is not an element with the same tag name as that of the token, then this is a parse error. */ // XERROR: implement parse error logic /* If the stack of open elements has an element in scope with the same tag name as that of the token, then pop elements from this stack until an element with that tag name has been popped from the stack. */ do { $node = array_pop($this->stack); } while ($node->tagName !== $token['name']); } else { // parse error } break; /* An end tag whose tag name is "form" */ case 'form': /* Let node be the element that the form element pointer is set to. */ $node = $this->form_pointer; /* Set the form element pointer to null. */ $this->form_pointer = null; /* If node is null or the stack of open elements does not * have node in scope, then this is a parse error; ignore the token. */ if ($node === null || !in_array($node, $this->stack)) { // parse error $this->ignored = true; } else { /* 1. Generate implied end tags. */ $this->generateImpliedEndTags(); /* 2. If the current node is not node, then this is a parse error. */ if (end($this->stack) !== $node) { // parse error } /* 3. Remove node from the stack of open elements. */ array_splice($this->stack, array_search($node, $this->stack, true), 1); } break; /* An end tag whose tag name is "p" */ case 'p': /* If the stack of open elements has a p element in scope, then generate implied end tags, except for p elements. */ if($this->elementInScope('p')) { /* Generate implied end tags, except for elements with * the same tag name as the token. */ $this->generateImpliedEndTags(array('p')); /* If the current node is not a p element, then this is a parse error. */ // XERROR: implement /* Pop elements from the stack of open elements until * an element with the same tag name as the token has * been popped from the stack. */ do { $node = array_pop($this->stack); } while ($node->tagName !== 'p'); } else { // parse error $this->emitToken(array( 'name' => 'p', 'type' => Tokenizer::STARTTAG, )); $this->emitToken($token); } break; /* An end tag whose tag name is "dd", "dt", or "li" */ case 'dd': case 'dt': case 'li': if($this->elementInScope($token['name'])) { $this->generateImpliedEndTags(array($token['name'])); /* If the current node is not an element with the same tag name as the token, then this is a parse error. */ // XERROR: implement parse error /* Pop elements from the stack of open elements until * an element with the same tag name as the token has * been popped from the stack. */ do { $node = array_pop($this->stack); } while ($node->tagName !== $token['name']); } else { // parse error } break; /* An end tag whose tag name is one of: "h1", "h2", "h3", "h4", "h5", "h6" */ case 'h1': case 'h2': case 'h3': case 'h4': case 'h5': case 'h6': $elements = array('h1', 'h2', 'h3', 'h4', 'h5', 'h6'); /* If the stack of open elements has in scope an element whose tag name is one of "h1", "h2", "h3", "h4", "h5", or "h6", then generate implied end tags. */ if($this->elementInScope($elements)) { $this->generateImpliedEndTags(); /* Now, if the current node is not an element with the same tag name as that of the token, then this is a parse error. */ // XERROR: implement parse error /* If the stack of open elements has in scope an element whose tag name is one of "h1", "h2", "h3", "h4", "h5", or "h6", then pop elements from the stack until an element with one of those tag names has been popped from the stack. */ do { $node = array_pop($this->stack); } while (!in_array($node->tagName, $elements)); } else { // parse error } break; /* An end tag whose tag name is one of: "a", "b", "big", "em", "font", "i", "nobr", "s", "small", "strike", "strong", "tt", "u" */ case 'a': case 'b': case 'big': case 'code': case 'em': case 'font': case 'i': case 'nobr': case 's': case 'small': case 'strike': case 'strong': case 'tt': case 'u': // XERROR: generally speaking this needs parse error logic /* 1. Let the formatting element be the last element in the list of active formatting elements that: * is between the end of the list and the last scope marker in the list, if any, or the start of the list otherwise, and * has the same tag name as the token. */ while(true) { for($a = count($this->a_formatting) - 1; $a >= 0; $a--) { if($this->a_formatting[$a] === self::MARKER) { break; } elseif($this->a_formatting[$a]->tagName === $token['name']) { $formatting_element = $this->a_formatting[$a]; $in_stack = in_array($formatting_element, $this->stack, true); $fe_af_pos = $a; break; } } /* If there is no such node, or, if that node is also in the stack of open elements but the element is not in scope, then this is a parse error. Abort these steps. The token is ignored. */ if(!isset($formatting_element) || ($in_stack && !$this->elementInScope($token['name']))) { $this->ignored = true; break; /* Otherwise, if there is such a node, but that node is not in the stack of open elements, then this is a parse error; remove the element from the list, and abort these steps. */ } elseif(isset($formatting_element) && !$in_stack) { unset($this->a_formatting[$fe_af_pos]); $this->a_formatting = array_merge($this->a_formatting); break; } /* Otherwise, there is a formatting element and that * element is in the stack and is in scope. If the * element is not the current node, this is a parse * error. In any case, proceed with the algorithm as * written in the following steps. */ // XERROR: implement me /* 2. Let the furthest block be the topmost node in the stack of open elements that is lower in the stack than the formatting element, and is not an element in the phrasing or formatting categories. There might not be one. */ $fe_s_pos = array_search($formatting_element, $this->stack, true); $length = count($this->stack); for($s = $fe_s_pos + 1; $s < $length; $s++) { $category = $this->getElementCategory($this->stack[$s]); if($category !== self::PHRASING && $category !== self::FORMATTING) { $furthest_block = $this->stack[$s]; break; } } /* 3. If there is no furthest block, then the UA must skip the subsequent steps and instead just pop all the nodes from the bottom of the stack of open elements, from the current node up to the formatting element, and remove the formatting element from the list of active formatting elements. */ if(!isset($furthest_block)) { for($n = $length - 1; $n >= $fe_s_pos; $n--) { array_pop($this->stack); } unset($this->a_formatting[$fe_af_pos]); $this->a_formatting = array_merge($this->a_formatting); break; } /* 4. Let the common ancestor be the element immediately above the formatting element in the stack of open elements. */ $common_ancestor = $this->stack[$fe_s_pos - 1]; /* 5. Let a bookmark note the position of the formatting element in the list of active formatting elements relative to the elements on either side of it in the list. */ $bookmark = $fe_af_pos; /* 6. Let node and last node be the furthest block. Follow these steps: */ $node = $furthest_block; $last_node = $furthest_block; while(true) { for($n = array_search($node, $this->stack, true) - 1; $n >= 0; $n--) { /* 6.1 Let node be the element immediately prior to node in the stack of open elements. */ $node = $this->stack[$n]; /* 6.2 If node is not in the list of active formatting elements, then remove node from the stack of open elements and then go back to step 1. */ if(!in_array($node, $this->a_formatting, true)) { array_splice($this->stack, $n, 1); } else { break; } } /* 6.3 Otherwise, if node is the formatting element, then go to the next step in the overall algorithm. */ if($node === $formatting_element) { break; /* 6.4 Otherwise, if last node is the furthest block, then move the aforementioned bookmark to be immediately after the node in the list of active formatting elements. */ } elseif($last_node === $furthest_block) { $bookmark = array_search($node, $this->a_formatting, true) + 1; } /* 6.5 Create an element for the token for which * the element node was created, replace the entry * for node in the list of active formatting * elements with an entry for the new element, * replace the entry for node in the stack of open * elements with an entry for the new element, and * let node be the new element. */ // we don't know what the token is anymore $clone = $node->cloneNode(); $a_pos = array_search($node, $this->a_formatting, true); $s_pos = array_search($node, $this->stack, true); $this->a_formatting[$a_pos] = $clone; $this->stack[$s_pos] = $clone; $node = $clone; /* 6.6 Insert last node into node, first removing it from its previous parent node if any. */ if($last_node->parentNode !== null) { $last_node->parentNode->removeChild($last_node); } $node->appendChild($last_node); /* 6.7 Let last node be node. */ $last_node = $node; /* 6.8 Return to step 1 of this inner set of steps. */ } /* 7. If the common ancestor node is a table, tbody, * tfoot, thead, or tr element, then, foster parent * whatever last node ended up being in the previous * step, first removing it from its previous parent * node if any. */ if ($last_node->parentNode) { // common step $last_node->parentNode->removeChild($last_node); } if (in_array($common_ancestor->tagName, array('table', 'tbody', 'tfoot', 'thead', 'tr'))) { $this->fosterParent($last_node); /* Otherwise, append whatever last node ended up being * in the previous step to the common ancestor node, * first removing it from its previous parent node if * any. */ } else { $common_ancestor->appendChild($last_node); } /* 8. Create an element for the token for which the * formatting element was created. */ $clone = $formatting_element->cloneNode(); /* 9. Take all of the child nodes of the furthest block and append them to the element created in the last step. */ while($furthest_block->hasChildNodes()) { $child = $furthest_block->firstChild; $furthest_block->removeChild($child); $clone->appendChild($child); } /* 10. Append that clone to the furthest block. */ $furthest_block->appendChild($clone); /* 11. Remove the formatting element from the list of active formatting elements, and insert the new element into the list of active formatting elements at the position of the aforementioned bookmark. */ $fe_af_pos = array_search($formatting_element, $this->a_formatting, true); array_splice($this->a_formatting, $fe_af_pos, 1); $af_part1 = array_slice($this->a_formatting, 0, $bookmark - 1); $af_part2 = array_slice($this->a_formatting, $bookmark); $this->a_formatting = array_merge($af_part1, array($clone), $af_part2); /* 12. Remove the formatting element from the stack of open elements, and insert the new element into the stack of open elements immediately below the position of the furthest block in that stack. */ $fe_s_pos = array_search($formatting_element, $this->stack, true); array_splice($this->stack, $fe_s_pos, 1); $fb_s_pos = array_search($furthest_block, $this->stack, true); $s_part1 = array_slice($this->stack, 0, $fb_s_pos + 1); $s_part2 = array_slice($this->stack, $fb_s_pos + 1); $this->stack = array_merge($s_part1, array($clone), $s_part2); /* 13. Jump back to step 1 in this series of steps. */ unset($formatting_element, $fe_af_pos, $fe_s_pos, $furthest_block); } break; case 'applet': case 'button': case 'marquee': case 'object': /* If the stack of open elements has an element in scope whose tag name matches the tag name of the token, then generate implied tags. */ if($this->elementInScope($token['name'])) { $this->generateImpliedEndTags(); /* Now, if the current node is not an element with the same tag name as the token, then this is a parse error. */ // XERROR: implement logic /* Pop elements from the stack of open elements until * an element with the same tag name as the token has * been popped from the stack. */ do { $node = array_pop($this->stack); } while ($node->tagName !== $token['name']); /* Clear the list of active formatting elements up to the * last marker. */ $keys = array_keys($this->a_formatting, self::MARKER, true); $marker = end($keys); for($n = count($this->a_formatting) - 1; $n > $marker; $n--) { array_pop($this->a_formatting); } } else { // parse error } break; case 'br': // Parse error $this->emitToken(array( 'name' => 'br', 'type' => Tokenizer::STARTTAG, )); break; /* An end tag token not covered by the previous entries */ default: for($n = count($this->stack) - 1; $n >= 0; $n--) { /* Initialise node to be the current node (the bottommost node of the stack). */ $node = $this->stack[$n]; /* If node has the same tag name as the end tag token, then: */ if($token['name'] === $node->tagName) { /* Generate implied end tags. */ $this->generateImpliedEndTags(); /* If the tag name of the end tag token does not match the tag name of the current node, this is a parse error. */ // XERROR: implement this /* Pop all the nodes from the current node up to node, including node, then stop these steps. */ // XSKETCHY do { $pop = array_pop($this->stack); } while ($pop !== $node); break; } else { $category = $this->getElementCategory($node); if($category !== self::FORMATTING && $category !== self::PHRASING) { /* Otherwise, if node is in neither the formatting category nor the phrasing category, then this is a parse error. Stop this algorithm. The end tag token is ignored. */ $this->ignored = true; break; // parse error } } /* Set node to the previous entry in the stack of open elements. Loop. */ } break; } break; } break; case self::IN_CDATA_RCDATA: if ( $token['type'] === Tokenizer::CHARACTER || $token['type'] === Tokenizer::SPACECHARACTER ) { $this->insertText($token['data']); } elseif ($token['type'] === Tokenizer::EOF) { // parse error /* If the current node is a script element, mark the script * element as "already executed". */ // probably not necessary array_pop($this->stack); $this->mode = $this->original_mode; $this->emitToken($token); } elseif ($token['type'] === Tokenizer::ENDTAG && $token['name'] === 'script') { array_pop($this->stack); $this->mode = $this->original_mode; // we're ignoring all of the execution stuff } elseif ($token['type'] === Tokenizer::ENDTAG) { array_pop($this->stack); $this->mode = $this->original_mode; } break; case self::IN_TABLE: $clear = array('html', 'table'); /* A character token that is one of one of U+0009 CHARACTER TABULATION, U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), or U+0020 SPACE */ if($token['type'] === Tokenizer::SPACECHARACTER && /* If the current table is tainted, then act as described in * the "anything else" entry below. */ // Note: hsivonen has a test that fails due to this line // because he wants to convince Hixie not to do taint !$this->currentTableIsTainted()) { /* Append the character to the current node. */ $this->insertText($token['data']); /* A comment token */ } elseif($token['type'] === Tokenizer::COMMENT) { /* Append a Comment node to the current node with the data attribute set to the data given in the comment token. */ $this->insertComment($token['data']); } elseif($token['type'] === Tokenizer::DOCTYPE) { // parse error /* A start tag whose tag name is "caption" */ } elseif($token['type'] === Tokenizer::STARTTAG && $token['name'] === 'caption') { /* Clear the stack back to a table context. */ $this->clearStackToTableContext($clear); /* Insert a marker at the end of the list of active formatting elements. */ $this->a_formatting[] = self::MARKER; /* Insert an HTML element for the token, then switch the insertion mode to "in caption". */ $this->insertElement($token); $this->mode = self::IN_CAPTION; /* A start tag whose tag name is "colgroup" */ } elseif($token['type'] === Tokenizer::STARTTAG && $token['name'] === 'colgroup') { /* Clear the stack back to a table context. */ $this->clearStackToTableContext($clear); /* Insert an HTML element for the token, then switch the insertion mode to "in column group". */ $this->insertElement($token); $this->mode = self::IN_COLUMN_GROUP; /* A start tag whose tag name is "col" */ } elseif($token['type'] === Tokenizer::STARTTAG && $token['name'] === 'col') { $this->emitToken(array( 'name' => 'colgroup', 'type' => Tokenizer::STARTTAG, 'attr' => array() )); $this->emitToken($token); /* A start tag whose tag name is one of: "tbody", "tfoot", "thead" */ } elseif($token['type'] === Tokenizer::STARTTAG && in_array($token['name'], array('tbody', 'tfoot', 'thead'))) { /* Clear the stack back to a table context. */ $this->clearStackToTableContext($clear); /* Insert an HTML element for the token, then switch the insertion mode to "in table body". */ $this->insertElement($token); $this->mode = self::IN_TABLE_BODY; /* A start tag whose tag name is one of: "td", "th", "tr" */ } elseif($token['type'] === Tokenizer::STARTTAG && in_array($token['name'], array('td', 'th', 'tr'))) { /* Act as if a start tag token with the tag name "tbody" had been seen, then reprocess the current token. */ $this->emitToken(array( 'name' => 'tbody', 'type' => Tokenizer::STARTTAG, 'attr' => array() )); $this->emitToken($token); /* A start tag whose tag name is "table" */ } elseif($token['type'] === Tokenizer::STARTTAG && $token['name'] === 'table') { /* Parse error. Act as if an end tag token with the tag name "table" had been seen, then, if that token wasn't ignored, reprocess the current token. */ $this->emitToken(array( 'name' => 'table', 'type' => Tokenizer::ENDTAG )); if (!$this->ignored) $this->emitToken($token); /* An end tag whose tag name is "table" */ } elseif($token['type'] === Tokenizer::ENDTAG && $token['name'] === 'table') { /* If the stack of open elements does not have an element in table scope with the same tag name as the token, this is a parse error. Ignore the token. (fragment case) */ if(!$this->elementInScope($token['name'], true)) { $this->ignored = true; /* Otherwise: */ } else { do { $node = array_pop($this->stack); } while ($node->tagName !== 'table'); /* Reset the insertion mode appropriately. */ $this->resetInsertionMode(); } /* An end tag whose tag name is one of: "body", "caption", "col", "colgroup", "html", "tbody", "td", "tfoot", "th", "thead", "tr" */ } elseif($token['type'] === Tokenizer::ENDTAG && in_array($token['name'], array('body', 'caption', 'col', 'colgroup', 'html', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr'))) { // Parse error. Ignore the token. } elseif($token['type'] === Tokenizer::STARTTAG && ($token['name'] === 'style' || $token['name'] === 'script')) { $this->processWithRulesFor($token, self::IN_HEAD); } elseif ($token['type'] === Tokenizer::STARTTAG && $token['name'] === 'input' && // assignment is intentional /* If the token does not have an attribute with the name "type", or * if it does, but that attribute's value is not an ASCII * case-insensitive match for the string "hidden", then: act as * described in the "anything else" entry below. */ ($type = $this->getAttr($token, 'type')) && strtolower($type) === 'hidden') { // I.e., if its an input with the type attribute == 'hidden' /* Otherwise */ // parse error $this->insertElement($token); array_pop($this->stack); } elseif ($token['type'] === Tokenizer::EOF) { /* If the current node is not the root html element, then this is a parse error. */ if (end($this->stack)->tagName !== 'html') { // Note: It can only be the current node in the fragment case. // parse error } /* Stop parsing. */ /* Anything else */ } else { /* Parse error. Process the token as if the insertion mode was "in body", with the following exception: */ $old = $this->foster_parent; $this->foster_parent = true; $this->processWithRulesFor($token, self::IN_BODY); $this->foster_parent = $old; } break; case self::IN_CAPTION: /* An end tag whose tag name is "caption" */ if($token['type'] === Tokenizer::ENDTAG && $token['name'] === 'caption') { /* If the stack of open elements does not have an element in table scope with the same tag name as the token, this is a parse error. Ignore the token. (fragment case) */ if(!$this->elementInScope($token['name'], true)) { $this->ignored = true; // Ignore /* Otherwise: */ } else { /* Generate implied end tags. */ $this->generateImpliedEndTags(); /* Now, if the current node is not a caption element, then this is a parse error. */ // XERROR: implement /* Pop elements from this stack until a caption element has been popped from the stack. */ do { $node = array_pop($this->stack); } while ($node->tagName !== 'caption'); /* Clear the list of active formatting elements up to the last marker. */ $this->clearTheActiveFormattingElementsUpToTheLastMarker(); /* Switch the insertion mode to "in table". */ $this->mode = self::IN_TABLE; } /* A start tag whose tag name is one of: "caption", "col", "colgroup", "tbody", "td", "tfoot", "th", "thead", "tr", or an end tag whose tag name is "table" */ } elseif(($token['type'] === Tokenizer::STARTTAG && in_array($token['name'], array('caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr'))) || ($token['type'] === Tokenizer::ENDTAG && $token['name'] === 'table')) { /* Parse error. Act as if an end tag with the tag name "caption" had been seen, then, if that token wasn't ignored, reprocess the current token. */ $this->emitToken(array( 'name' => 'caption', 'type' => Tokenizer::ENDTAG )); if (!$this->ignored) $this->emitToken($token); /* An end tag whose tag name is one of: "body", "col", "colgroup", "html", "tbody", "td", "tfoot", "th", "thead", "tr" */ } elseif($token['type'] === Tokenizer::ENDTAG && in_array($token['name'], array('body', 'col', 'colgroup', 'html', 'tbody', 'tfoot', 'th', 'thead', 'tr'))) { // Parse error. Ignore the token. $this->ignored = true; /* Anything else */ } else { /* Process the token as if the insertion mode was "in body". */ $this->processWithRulesFor($token, self::IN_BODY); } break; case self::IN_COLUMN_GROUP: /* A character token that is one of one of U+0009 CHARACTER TABULATION, U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), or U+0020 SPACE */ if($token['type'] === Tokenizer::SPACECHARACTER) { /* Append the character to the current node. */ $this->insertText($token['data']); /* A comment token */ } elseif($token['type'] === Tokenizer::COMMENT) { /* Append a Comment node to the current node with the data attribute set to the data given in the comment token. */ $this->insertToken($token['data']); } elseif($token['type'] === Tokenizer::DOCTYPE) { // parse error } elseif($token['type'] === Tokenizer::STARTTAG && $token['name'] === 'html') { $this->processWithRulesFor($token, self::IN_BODY); /* A start tag whose tag name is "col" */ } elseif($token['type'] === Tokenizer::STARTTAG && $token['name'] === 'col') { /* Insert a col element for the token. Immediately pop the current node off the stack of open elements. */ $this->insertElement($token); array_pop($this->stack); // XERROR: Acknowledge the token's self-closing flag, if it is set. /* An end tag whose tag name is "colgroup" */ } elseif($token['type'] === Tokenizer::ENDTAG && $token['name'] === 'colgroup') { /* If the current node is the root html element, then this is a parse error, ignore the token. (fragment case) */ if(end($this->stack)->tagName === 'html') { $this->ignored = true; /* Otherwise, pop the current node (which will be a colgroup element) from the stack of open elements. Switch the insertion mode to "in table". */ } else { array_pop($this->stack); $this->mode = self::IN_TABLE; } /* An end tag whose tag name is "col" */ } elseif($token['type'] === Tokenizer::ENDTAG && $token['name'] === 'col') { /* Parse error. Ignore the token. */ $this->ignored = true; /* An end-of-file token */ /* If the current node is the root html element */ } elseif($token['type'] === Tokenizer::EOF && end($this->stack)->tagName === 'html') { /* Stop parsing */ /* Anything else */ } else { /* Act as if an end tag with the tag name "colgroup" had been seen, and then, if that token wasn't ignored, reprocess the current token. */ $this->emitToken(array( 'name' => 'colgroup', 'type' => Tokenizer::ENDTAG )); if (!$this->ignored) $this->emitToken($token); } break; case self::IN_TABLE_BODY: $clear = array('tbody', 'tfoot', 'thead', 'html'); /* A start tag whose tag name is "tr" */ if($token['type'] === Tokenizer::STARTTAG && $token['name'] === 'tr') { /* Clear the stack back to a table body context. */ $this->clearStackToTableContext($clear); /* Insert a tr element for the token, then switch the insertion mode to "in row". */ $this->insertElement($token); $this->mode = self::IN_ROW; /* A start tag whose tag name is one of: "th", "td" */ } elseif($token['type'] === Tokenizer::STARTTAG && ($token['name'] === 'th' || $token['name'] === 'td')) { /* Parse error. Act as if a start tag with the tag name "tr" had been seen, then reprocess the current token. */ $this->emitToken(array( 'name' => 'tr', 'type' => Tokenizer::STARTTAG, 'attr' => array() )); $this->emitToken($token); /* An end tag whose tag name is one of: "tbody", "tfoot", "thead" */ } elseif($token['type'] === Tokenizer::ENDTAG && in_array($token['name'], array('tbody', 'tfoot', 'thead'))) { /* If the stack of open elements does not have an element in table scope with the same tag name as the token, this is a parse error. Ignore the token. */ if(!$this->elementInScope($token['name'], true)) { // Parse error $this->ignored = true; /* Otherwise: */ } else { /* Clear the stack back to a table body context. */ $this->clearStackToTableContext($clear); /* Pop the current node from the stack of open elements. Switch the insertion mode to "in table". */ array_pop($this->stack); $this->mode = self::IN_TABLE; } /* A start tag whose tag name is one of: "caption", "col", "colgroup", "tbody", "tfoot", "thead", or an end tag whose tag name is "table" */ } elseif(($token['type'] === Tokenizer::STARTTAG && in_array($token['name'], array('caption', 'col', 'colgroup', 'tbody', 'tfoot', 'thead'))) || ($token['type'] === Tokenizer::ENDTAG && $token['name'] === 'table')) { /* If the stack of open elements does not have a tbody, thead, or tfoot element in table scope, this is a parse error. Ignore the token. (fragment case) */ if(!$this->elementInScope(array('tbody', 'thead', 'tfoot'), true)) { // parse error $this->ignored = true; /* Otherwise: */ } else { /* Clear the stack back to a table body context. */ $this->clearStackToTableContext($clear); /* Act as if an end tag with the same tag name as the current node ("tbody", "tfoot", or "thead") had been seen, then reprocess the current token. */ $this->emitToken(array( 'name' => end($this->stack)->tagName, 'type' => Tokenizer::ENDTAG )); $this->emitToken($token); } /* An end tag whose tag name is one of: "body", "caption", "col", "colgroup", "html", "td", "th", "tr" */ } elseif($token['type'] === Tokenizer::ENDTAG && in_array($token['name'], array('body', 'caption', 'col', 'colgroup', 'html', 'td', 'th', 'tr'))) { /* Parse error. Ignore the token. */ $this->ignored = true; /* Anything else */ } else { /* Process the token as if the insertion mode was "in table". */ $this->processWithRulesFor($token, self::IN_TABLE); } break; case self::IN_ROW: $clear = array('tr', 'html'); /* A start tag whose tag name is one of: "th", "td" */ if($token['type'] === Tokenizer::STARTTAG && ($token['name'] === 'th' || $token['name'] === 'td')) { /* Clear the stack back to a table row context. */ $this->clearStackToTableContext($clear); /* Insert an HTML element for the token, then switch the insertion mode to "in cell". */ $this->insertElement($token); $this->mode = self::IN_CELL; /* Insert a marker at the end of the list of active formatting elements. */ $this->a_formatting[] = self::MARKER; /* An end tag whose tag name is "tr" */ } elseif($token['type'] === Tokenizer::ENDTAG && $token['name'] === 'tr') { /* If the stack of open elements does not have an element in table scope with the same tag name as the token, this is a parse error. Ignore the token. (fragment case) */ if(!$this->elementInScope($token['name'], true)) { // Ignore. $this->ignored = true; /* Otherwise: */ } else { /* Clear the stack back to a table row context. */ $this->clearStackToTableContext($clear); /* Pop the current node (which will be a tr element) from the stack of open elements. Switch the insertion mode to "in table body". */ array_pop($this->stack); $this->mode = self::IN_TABLE_BODY; } /* A start tag whose tag name is one of: "caption", "col", "colgroup", "tbody", "tfoot", "thead", "tr" or an end tag whose tag name is "table" */ } elseif(($token['type'] === Tokenizer::STARTTAG && in_array($token['name'], array('caption', 'col', 'colgroup', 'tbody', 'tfoot', 'thead', 'tr'))) || ($token['type'] === Tokenizer::ENDTAG && $token['name'] === 'table')) { /* Act as if an end tag with the tag name "tr" had been seen, then, if that token wasn't ignored, reprocess the current token. */ $this->emitToken(array( 'name' => 'tr', 'type' => Tokenizer::ENDTAG )); if (!$this->ignored) $this->emitToken($token); /* An end tag whose tag name is one of: "tbody", "tfoot", "thead" */ } elseif($token['type'] === Tokenizer::ENDTAG && in_array($token['name'], array('tbody', 'tfoot', 'thead'))) { /* If the stack of open elements does not have an element in table scope with the same tag name as the token, this is a parse error. Ignore the token. */ if(!$this->elementInScope($token['name'], true)) { $this->ignored = true; /* Otherwise: */ } else { /* Otherwise, act as if an end tag with the tag name "tr" had been seen, then reprocess the current token. */ $this->emitToken(array( 'name' => 'tr', 'type' => Tokenizer::ENDTAG )); $this->emitToken($token); } /* An end tag whose tag name is one of: "body", "caption", "col", "colgroup", "html", "td", "th" */ } elseif($token['type'] === Tokenizer::ENDTAG && in_array($token['name'], array('body', 'caption', 'col', 'colgroup', 'html', 'td', 'th'))) { /* Parse error. Ignore the token. */ $this->ignored = true; /* Anything else */ } else { /* Process the token as if the insertion mode was "in table". */ $this->processWithRulesFor($token, self::IN_TABLE); } break; case self::IN_CELL: /* An end tag whose tag name is one of: "td", "th" */ if($token['type'] === Tokenizer::ENDTAG && ($token['name'] === 'td' || $token['name'] === 'th')) { /* If the stack of open elements does not have an element in table scope with the same tag name as that of the token, then this is a parse error and the token must be ignored. */ if(!$this->elementInScope($token['name'], true)) { $this->ignored = true; /* Otherwise: */ } else { /* Generate implied end tags, except for elements with the same tag name as the token. */ $this->generateImpliedEndTags(array($token['name'])); /* Now, if the current node is not an element with the same tag name as the token, then this is a parse error. */ // XERROR: Implement parse error code /* Pop elements from this stack until an element with the same tag name as the token has been popped from the stack. */ do { $node = array_pop($this->stack); } while ($node->tagName !== $token['name']); /* Clear the list of active formatting elements up to the last marker. */ $this->clearTheActiveFormattingElementsUpToTheLastMarker(); /* Switch the insertion mode to "in row". (The current node will be a tr element at this point.) */ $this->mode = self::IN_ROW; } /* A start tag whose tag name is one of: "caption", "col", "colgroup", "tbody", "td", "tfoot", "th", "thead", "tr" */ } elseif($token['type'] === Tokenizer::STARTTAG && in_array($token['name'], array('caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr'))) { /* If the stack of open elements does not have a td or th element in table scope, then this is a parse error; ignore the token. (fragment case) */ if(!$this->elementInScope(array('td', 'th'), true)) { // parse error $this->ignored = true; /* Otherwise, close the cell (see below) and reprocess the current token. */ } else { $this->closeCell(); $this->emitToken($token); } /* An end tag whose tag name is one of: "body", "caption", "col", "colgroup", "html" */ } elseif($token['type'] === Tokenizer::ENDTAG && in_array($token['name'], array('body', 'caption', 'col', 'colgroup', 'html'))) { /* Parse error. Ignore the token. */ $this->ignored = true; /* An end tag whose tag name is one of: "table", "tbody", "tfoot", "thead", "tr" */ } elseif($token['type'] === Tokenizer::ENDTAG && in_array($token['name'], array('table', 'tbody', 'tfoot', 'thead', 'tr'))) { /* If the stack of open elements does not have a td or th element in table scope, then this is a parse error; ignore the token. (innerHTML case) */ if(!$this->elementInScope(array('td', 'th'), true)) { // Parse error $this->ignored = true; /* Otherwise, close the cell (see below) and reprocess the current token. */ } else { $this->closeCell(); $this->emitToken($token); } /* Anything else */ } else { /* Process the token as if the insertion mode was "in body". */ $this->processWithRulesFor($token, self::IN_BODY); } break; case self::IN_SELECT: /* Handle the token as follows: */ /* A character token */ if( $token['type'] === Tokenizer::CHARACTER || $token['type'] === Tokenizer::SPACECHARACTER ) { /* Append the token's character to the current node. */ $this->insertText($token['data']); /* A comment token */ } elseif($token['type'] === Tokenizer::COMMENT) { /* Append a Comment node to the current node with the data attribute set to the data given in the comment token. */ $this->insertComment($token['data']); } elseif($token['type'] === Tokenizer::DOCTYPE) { // parse error } elseif($token['type'] === Tokenizer::STARTTAG && $token['name'] === 'html') { $this->processWithRulesFor($token, self::INBODY); /* A start tag token whose tag name is "option" */ } elseif($token['type'] === Tokenizer::STARTTAG && $token['name'] === 'option') { /* If the current node is an option element, act as if an end tag with the tag name "option" had been seen. */ if(end($this->stack)->tagName === 'option') { $this->emitToken(array( 'name' => 'option', 'type' => Tokenizer::ENDTAG )); } /* Insert an HTML element for the token. */ $this->insertElement($token); /* A start tag token whose tag name is "optgroup" */ } elseif($token['type'] === Tokenizer::STARTTAG && $token['name'] === 'optgroup') { /* If the current node is an option element, act as if an end tag with the tag name "option" had been seen. */ if(end($this->stack)->tagName === 'option') { $this->emitToken(array( 'name' => 'option', 'type' => Tokenizer::ENDTAG )); } /* If the current node is an optgroup element, act as if an end tag with the tag name "optgroup" had been seen. */ if(end($this->stack)->tagName === 'optgroup') { $this->emitToken(array( 'name' => 'optgroup', 'type' => Tokenizer::ENDTAG )); } /* Insert an HTML element for the token. */ $this->insertElement($token); /* An end tag token whose tag name is "optgroup" */ } elseif($token['type'] === Tokenizer::ENDTAG && $token['name'] === 'optgroup') { /* First, if the current node is an option element, and the node immediately before it in the stack of open elements is an optgroup element, then act as if an end tag with the tag name "option" had been seen. */ $elements_in_stack = count($this->stack); if($this->stack[$elements_in_stack - 1]->tagName === 'option' && $this->stack[$elements_in_stack - 2]->tagName === 'optgroup') { $this->emitToken(array( 'name' => 'option', 'type' => Tokenizer::ENDTAG )); } /* If the current node is an optgroup element, then pop that node from the stack of open elements. Otherwise, this is a parse error, ignore the token. */ if(end($this->stack)->tagName === 'optgroup') { array_pop($this->stack); } else { // parse error $this->ignored = true; } /* An end tag token whose tag name is "option" */ } elseif($token['type'] === Tokenizer::ENDTAG && $token['name'] === 'option') { /* If the current node is an option element, then pop that node from the stack of open elements. Otherwise, this is a parse error, ignore the token. */ if(end($this->stack)->tagName === 'option') { array_pop($this->stack); } else { // parse error $this->ignored = true; } /* An end tag whose tag name is "select" */ } elseif($token['type'] === Tokenizer::ENDTAG && $token['name'] === 'select') { /* If the stack of open elements does not have an element in table scope with the same tag name as the token, this is a parse error. Ignore the token. (fragment case) */ if(!$this->elementInScope($token['name'], true)) { $this->ignored = true; // parse error /* Otherwise: */ } else { /* Pop elements from the stack of open elements until a select element has been popped from the stack. */ do { $node = array_pop($this->stack); } while ($node->tagName !== 'select'); /* Reset the insertion mode appropriately. */ $this->resetInsertionMode(); } /* A start tag whose tag name is "select" */ } elseif($token['type'] === Tokenizer::STARTTAG && $token['name'] === 'select') { /* Parse error. Act as if the token had been an end tag with the tag name "select" instead. */ $this->emitToken(array( 'name' => 'select', 'type' => Tokenizer::ENDTAG )); } elseif($token['type'] === Tokenizer::STARTTAG && ($token['name'] === 'input' || $token['name'] === 'textarea')) { // parse error $this->emitToken(array( 'name' => 'select', 'type' => Tokenizer::ENDTAG )); $this->emitToken($token); } elseif($token['type'] === Tokenizer::STARTTAG && $token['name'] === 'script') { $this->processWithRulesFor($token, self::IN_HEAD); } elseif($token['type'] === Tokenizer::EOF) { // XERROR: If the current node is not the root html element, then this is a parse error. /* Stop parsing */ /* Anything else */ } else { /* Parse error. Ignore the token. */ $this->ignored = true; } break; case self::IN_SELECT_IN_TABLE: if($token['type'] === Tokenizer::STARTTAG && in_array($token['name'], array('caption', 'table', 'tbody', 'tfoot', 'thead', 'tr', 'td', 'th'))) { // parse error $this->emitToken(array( 'name' => 'select', 'type' => Tokenizer::ENDTAG, )); $this->emitToken($token); /* An end tag whose tag name is one of: "caption", "table", "tbody", "tfoot", "thead", "tr", "td", "th" */ } elseif($token['type'] === Tokenizer::ENDTAG && in_array($token['name'], array('caption', 'table', 'tbody', 'tfoot', 'thead', 'tr', 'td', 'th'))) { /* Parse error. */ // parse error /* If the stack of open elements has an element in table scope with the same tag name as that of the token, then act as if an end tag with the tag name "select" had been seen, and reprocess the token. Otherwise, ignore the token. */ if($this->elementInScope($token['name'], true)) { $this->emitToken(array( 'name' => 'select', 'type' => Tokenizer::ENDTAG )); $this->emitToken($token); } else { $this->ignored = true; } } else { $this->processWithRulesFor($token, self::IN_SELECT); } break; case self::IN_FOREIGN_CONTENT: if ($token['type'] === Tokenizer::CHARACTER || $token['type'] === Tokenizer::SPACECHARACTER) { $this->insertText($token['data']); } elseif ($token['type'] === Tokenizer::COMMENT) { $this->insertComment($token['data']); } elseif ($token['type'] === Tokenizer::DOCTYPE) { // XERROR: parse error } elseif ($token['type'] === Tokenizer::ENDTAG && $token['name'] === 'script' && end($this->stack)->tagName === 'script' && end($this->stack)->namespaceURI === self::NS_SVG) { array_pop($this->stack); // a bunch of script running mumbo jumbo } elseif ( ($token['type'] === Tokenizer::STARTTAG && (( $token['name'] !== 'mglyph' && $token['name'] !== 'malignmark' && end($this->stack)->namespaceURI === self::NS_MATHML && in_array(end($this->stack)->tagName, array('mi', 'mo', 'mn', 'ms', 'mtext')) ) || ( $token['name'] === 'svg' && end($this->stack)->namespaceURI === self::NS_MATHML && end($this->stack)->tagName === 'annotation-xml' ) || ( end($this->stack)->namespaceURI === self::NS_SVG && in_array(end($this->stack)->tagName, array('foreignObject', 'desc', 'title')) ) || ( // XSKETCHY end($this->stack)->namespaceURI === self::NS_HTML )) ) || $token['type'] === Tokenizer::ENDTAG ) { $this->processWithRulesFor($token, $this->secondary_mode); /* If, after doing so, the insertion mode is still "in foreign * content", but there is no element in scope that has a namespace * other than the HTML namespace, switch the insertion mode to the * secondary insertion mode. */ if ($this->mode === self::IN_FOREIGN_CONTENT) { $found = false; // this basically duplicates elementInScope() for ($i = count($this->stack) - 1; $i >= 0; $i--) { $node = $this->stack[$i]; if ($node->namespaceURI !== self::NS_HTML) { $found = true; break; } elseif (in_array($node->tagName, array('table', 'html', 'applet', 'caption', 'td', 'th', 'button', 'marquee', 'object')) || ($node->tagName === 'foreignObject' && $node->namespaceURI === self::NS_SVG)) { break; } } if (!$found) { $this->mode = $this->secondary_mode; } } } elseif ($token['type'] === Tokenizer::EOF || ( $token['type'] === Tokenizer::STARTTAG && (in_array($token['name'], array('b', "big", "blockquote", "body", "br", "center", "code", "dd", "div", "dl", "dt", "em", "embed", "h1", "h2", "h3", "h4", "h5", "h6", "head", "hr", "i", "img", "li", "listing", "menu", "meta", "nobr", "ol", "p", "pre", "ruby", "s", "small", "span", "strong", "strike", "sub", "sup", "table", "tt", "u", "ul", "var")) || ($token['name'] === 'font' && ($this->getAttr($token, 'color') || $this->getAttr($token, 'face') || $this->getAttr($token, 'size')))))) { // XERROR: parse error do { $node = array_pop($this->stack); } while ($node->namespaceURI !== self::NS_HTML); $this->stack[] = $node; $this->mode = $this->secondary_mode; $this->emitToken($token); } elseif ($token['type'] === Tokenizer::STARTTAG) { static $svg_lookup = array( 'altglyph' => 'altGlyph', 'altglyphdef' => 'altGlyphDef', 'altglyphitem' => 'altGlyphItem', 'animatecolor' => 'animateColor', 'animatemotion' => 'animateMotion', 'animatetransform' => 'animateTransform', 'clippath' => 'clipPath', 'feblend' => 'feBlend', 'fecolormatrix' => 'feColorMatrix', 'fecomponenttransfer' => 'feComponentTransfer', 'fecomposite' => 'feComposite', 'feconvolvematrix' => 'feConvolveMatrix', 'fediffuselighting' => 'feDiffuseLighting', 'fedisplacementmap' => 'feDisplacementMap', 'fedistantlight' => 'feDistantLight', 'feflood' => 'feFlood', 'fefunca' => 'feFuncA', 'fefuncb' => 'feFuncB', 'fefuncg' => 'feFuncG', 'fefuncr' => 'feFuncR', 'fegaussianblur' => 'feGaussianBlur', 'feimage' => 'feImage', 'femerge' => 'feMerge', 'femergenode' => 'feMergeNode', 'femorphology' => 'feMorphology', 'feoffset' => 'feOffset', 'fepointlight' => 'fePointLight', 'fespecularlighting' => 'feSpecularLighting', 'fespotlight' => 'feSpotLight', 'fetile' => 'feTile', 'feturbulence' => 'feTurbulence', 'foreignobject' => 'foreignObject', 'glyphref' => 'glyphRef', 'lineargradient' => 'linearGradient', 'radialgradient' => 'radialGradient', 'textpath' => 'textPath', ); $current = end($this->stack); if ($current->namespaceURI === self::NS_MATHML) { $token = $this->adjustMathMLAttributes($token); } if ($current->namespaceURI === self::NS_SVG && isset($svg_lookup[$token['name']])) { $token['name'] = $svg_lookup[$token['name']]; } if ($current->namespaceURI === self::NS_SVG) { $token = $this->adjustSVGAttributes($token); } $token = $this->adjustForeignAttributes($token); $this->insertForeignElement($token, $current->namespaceURI); if (isset($token['self-closing'])) { array_pop($this->stack); // XERROR: acknowledge self-closing flag } } break; case self::AFTER_BODY: /* Handle the token as follows: */ /* A character token that is one of one of U+0009 CHARACTER TABULATION, U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), or U+0020 SPACE */ if($token['type'] === Tokenizer::SPACECHARACTER) { /* Process the token as it would be processed if the insertion mode was "in body". */ $this->processWithRulesFor($token, self::IN_BODY); /* A comment token */ } elseif($token['type'] === Tokenizer::COMMENT) { /* Append a Comment node to the first element in the stack of open elements (the html element), with the data attribute set to the data given in the comment token. */ $comment = $this->dom->createComment($token['data']); $this->stack[0]->appendChild($comment); } elseif($token['type'] === Tokenizer::DOCTYPE) { // parse error } elseif($token['type'] === Tokenizer::STARTTAG && $token['name'] === 'html') { $this->processWithRulesFor($token, self::IN_BODY); /* An end tag with the tag name "html" */ } elseif($token['type'] === Tokenizer::ENDTAG && $token['name'] === 'html') { /* If the parser was originally created as part of the HTML * fragment parsing algorithm, this is a parse error; ignore * the token. (fragment case) */ $this->ignored = true; // XERROR: implement this $this->mode = self::AFTER_AFTER_BODY; } elseif($token['type'] === Tokenizer::EOF) { /* Stop parsing */ /* Anything else */ } else { /* Parse error. Set the insertion mode to "in body" and reprocess the token. */ $this->mode = self::IN_BODY; $this->emitToken($token); } break; case self::IN_FRAMESET: /* Handle the token as follows: */ /* A character token that is one of one of U+0009 CHARACTER TABULATION, U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), U+000D CARRIAGE RETURN (CR), or U+0020 SPACE */ if($token['type'] === Tokenizer::SPACECHARACTER) { /* Append the character to the current node. */ $this->insertText($token['data']); /* A comment token */ } elseif($token['type'] === Tokenizer::COMMENT) { /* Append a Comment node to the current node with the data attribute set to the data given in the comment token. */ $this->insertComment($token['data']); } elseif($token['type'] === Tokenizer::DOCTYPE) { // parse error /* A start tag with the tag name "frameset" */ } elseif($token['type'] === Tokenizer::STARTTAG && $token['name'] === 'frameset') { $this->insertElement($token); /* An end tag with the tag name "frameset" */ } elseif($token['type'] === Tokenizer::ENDTAG && $token['name'] === 'frameset') { /* If the current node is the root html element, then this is a parse error; ignore the token. (fragment case) */ if(end($this->stack)->tagName === 'html') { $this->ignored = true; // Parse error } else { /* Otherwise, pop the current node from the stack of open elements. */ array_pop($this->stack); /* If the parser was not originally created as part of the HTML * fragment parsing algorithm (fragment case), and the current * node is no longer a frameset element, then switch the * insertion mode to "after frameset". */ $this->mode = self::AFTER_FRAMESET; } /* A start tag with the tag name "frame" */ } elseif($token['type'] === Tokenizer::STARTTAG && $token['name'] === 'frame') { /* Insert an HTML element for the token. */ $this->insertElement($token); /* Immediately pop the current node off the stack of open elements. */ array_pop($this->stack); // XERROR: Acknowledge the token's self-closing flag, if it is set. /* A start tag with the tag name "noframes" */ } elseif($token['type'] === Tokenizer::STARTTAG && $token['name'] === 'noframes') { /* Process the token using the rules for the "in head" insertion mode. */ $this->processwithRulesFor($token, self::IN_HEAD); } elseif($token['type'] === Tokenizer::EOF) { // XERROR: If the current node is not the root html element, then this is a parse error. /* Stop parsing */ /* Anything else */ } else { /* Parse error. Ignore the token. */ $this->ignored = true; } break; case self::AFTER_FRAMESET: /* Handle the token as follows: */ /* A character token that is one of one of U+0009 CHARACTER TABULATION, U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), U+000D CARRIAGE RETURN (CR), or U+0020 SPACE */ if($token['type'] === Tokenizer::SPACECHARACTER) { /* Append the character to the current node. */ $this->insertText($token['data']); /* A comment token */ } elseif($token['type'] === Tokenizer::COMMENT) { /* Append a Comment node to the current node with the data attribute set to the data given in the comment token. */ $this->insertComment($token['data']); } elseif($token['type'] === Tokenizer::DOCTYPE) { // parse error } elseif($token['type'] === Tokenizer::STARTTAG && $token['name'] === 'html') { $this->processWithRulesFor($token, self::IN_BODY); /* An end tag with the tag name "html" */ } elseif($token['type'] === Tokenizer::ENDTAG && $token['name'] === 'html') { $this->mode = self::AFTER_AFTER_FRAMESET; /* A start tag with the tag name "noframes" */ } elseif($token['type'] === Tokenizer::STARTTAG && $token['name'] === 'noframes') { $this->processWithRulesFor($token, self::IN_HEAD); } elseif($token['type'] === Tokenizer::EOF) { /* Stop parsing */ /* Anything else */ } else { /* Parse error. Ignore the token. */ $this->ignored = true; } break; case self::AFTER_AFTER_BODY: /* A comment token */ if($token['type'] === Tokenizer::COMMENT) { /* Append a Comment node to the Document object with the data attribute set to the data given in the comment token. */ $comment = $this->dom->createComment($token['data']); $this->dom->appendChild($comment); } elseif($token['type'] === Tokenizer::DOCTYPE || $token['type'] === Tokenizer::SPACECHARACTER || ($token['type'] === Tokenizer::STARTTAG && $token['name'] === 'html')) { $this->processWithRulesFor($token, self::IN_BODY); /* An end-of-file token */ } elseif($token['type'] === Tokenizer::EOF) { /* OMG DONE!! */ } else { // parse error $this->mode = self::IN_BODY; $this->emitToken($token); } break; case self::AFTER_AFTER_FRAMESET: /* A comment token */ if($token['type'] === Tokenizer::COMMENT) { /* Append a Comment node to the Document object with the data attribute set to the data given in the comment token. */ $comment = $this->dom->createComment($token['data']); $this->dom->appendChild($comment); } elseif($token['type'] === Tokenizer::DOCTYPE || $token['type'] === Tokenizer::SPACECHARACTER || ($token['type'] === Tokenizer::STARTTAG && $token['name'] === 'html')) { $this->processWithRulesFor($token, self::IN_BODY); /* An end-of-file token */ } elseif($token['type'] === Tokenizer::EOF) { /* OMG DONE!! */ } elseif($token['type'] === Tokenizer::STARTTAG && $token['name'] === 'nofrmaes') { $this->processWithRulesFor($token, self::IN_HEAD); } else { // parse error } break; } // end funky indenting } private function insertElement($token, $append = true) { $el = $this->dom->createElementNS(self::NS_HTML, $token['name']); if (!empty($token['attr'])) { foreach($token['attr'] as $attr) { if(!$el->hasAttribute($attr['name'])) { $el->setAttribute($attr['name'], $attr['value']); } } } if ($append) { $this->appendToRealParent($el); $this->stack[] = $el; } return $el; } private function insertText($data) { if ($data === '') return; if ($this->ignore_lf_token) { if ($data[0] === "\n") { $data = substr($data, 1); if ($data === false) return; } } $text = $this->dom->createTextNode($data); $this->appendToRealParent($text); } private function insertComment($data) { $comment = $this->dom->createComment($data); $this->appendToRealParent($comment); } private function appendToRealParent($node) { // this is only for the foster_parent case /* If the current node is a table, tbody, tfoot, thead, or tr element, then, whenever a node would be inserted into the current node, it must instead be inserted into the foster parent element. */ if(!$this->foster_parent || !in_array(end($this->stack)->tagName, array('table', 'tbody', 'tfoot', 'thead', 'tr'))) { end($this->stack)->appendChild($node); } else { $this->fosterParent($node); } } private function elementInScope($el, $table = false) { if(is_array($el)) { foreach($el as $element) { if($this->elementInScope($element, $table)) { return true; } } return false; } $leng = count($this->stack); for($n = 0; $n < $leng; $n++) { /* 1. Initialise node to be the current node (the bottommost node of the stack). */ $node = $this->stack[$leng - 1 - $n]; if($node->tagName === $el) { /* 2. If node is the target node, terminate in a match state. */ return true; // these are the common states for "in scope" and "in table scope" } elseif($node->tagName === 'table' || $node->tagName === 'html') { return false; // these are only valid for "in scope" } elseif(!$table && (in_array($node->tagName, array('applet', 'caption', 'td', 'th', 'button', 'marquee', 'object')) || $node->tagName === 'foreignObject' && $node->namespaceURI === self::NS_SVG)) { return false; } /* Otherwise, set node to the previous entry in the stack of open elements and return to step 2. (This will never fail, since the loop will always terminate in the previous step if the top of the stack is reached.) */ } } private function reconstructActiveFormattingElements() { /* 1. If there are no entries in the list of active formatting elements, then there is nothing to reconstruct; stop this algorithm. */ $formatting_elements = count($this->a_formatting); if($formatting_elements === 0) { return false; } /* 3. Let entry be the last (most recently added) element in the list of active formatting elements. */ $entry = end($this->a_formatting); /* 2. If the last (most recently added) entry in the list of active formatting elements is a marker, or if it is an element that is in the stack of open elements, then there is nothing to reconstruct; stop this algorithm. */ if($entry === self::MARKER || in_array($entry, $this->stack, true)) { return false; } for($a = $formatting_elements - 1; $a >= 0; true) { /* 4. If there are no entries before entry in the list of active formatting elements, then jump to step 8. */ if($a === 0) { $step_seven = false; break; } /* 5. Let entry be the entry one earlier than entry in the list of active formatting elements. */ $a--; $entry = $this->a_formatting[$a]; /* 6. If entry is neither a marker nor an element that is also in thetack of open elements, go to step 4. */ if($entry === self::MARKER || in_array($entry, $this->stack, true)) { break; } } while(true) { /* 7. Let entry be the element one later than entry in the list of active formatting elements. */ if(isset($step_seven) && $step_seven === true) { $a++; $entry = $this->a_formatting[$a]; } /* 8. Perform a shallow clone of the element entry to obtain clone. */ $clone = $entry->cloneNode(); /* 9. Append clone to the current node and push it onto the stack of open elements so that it is the new current node. */ $this->appendToRealParent($clone); $this->stack[] = $clone; /* 10. Replace the entry for entry in the list with an entry for clone. */ $this->a_formatting[$a] = $clone; /* 11. If the entry for clone in the list of active formatting elements is not the last entry in the list, return to step 7. */ if(end($this->a_formatting) !== $clone) { $step_seven = true; } else { break; } } } private function clearTheActiveFormattingElementsUpToTheLastMarker() { /* When the steps below require the UA to clear the list of active formatting elements up to the last marker, the UA must perform the following steps: */ while(true) { /* 1. Let entry be the last (most recently added) entry in the list of active formatting elements. */ $entry = end($this->a_formatting); /* 2. Remove entry from the list of active formatting elements. */ array_pop($this->a_formatting); /* 3. If entry was a marker, then stop the algorithm at this point. The list has been cleared up to the last marker. */ if($entry === self::MARKER) { break; } } } private function generateImpliedEndTags($exclude = array()) { /* When the steps below require the UA to generate implied end tags, then, if the current node is a dd element, a dt element, an li element, a p element, a td element, a th element, or a tr element, the UA must act as if an end tag with the respective tag name had been seen and then generate implied end tags again. */ $node = end($this->stack); $elements = array_diff(array('dd', 'dt', 'li', 'p', 'td', 'th', 'tr'), $exclude); while(in_array(end($this->stack)->tagName, $elements)) { array_pop($this->stack); } } private function getElementCategory($node) { if (!is_object($node)) debug_print_backtrace(); $name = $node->tagName; if(in_array($name, $this->special)) return self::SPECIAL; elseif(in_array($name, $this->scoping)) return self::SCOPING; elseif(in_array($name, $this->formatting)) return self::FORMATTING; else return self::PHRASING; } private function clearStackToTableContext($elements) { /* When the steps above require the UA to clear the stack back to a table context, it means that the UA must, while the current node is not a table element or an html element, pop elements from the stack of open elements. */ while(true) { $name = end($this->stack)->tagName; if(in_array($name, $elements)) { break; } else { array_pop($this->stack); } } } private function resetInsertionMode($context = null) { /* 1. Let last be false. */ $last = false; $leng = count($this->stack); for($n = $leng - 1; $n >= 0; $n--) { /* 2. Let node be the last node in the stack of open elements. */ $node = $this->stack[$n]; /* 3. If node is the first node in the stack of open elements, then * set last to true and set node to the context element. (fragment * case) */ if($this->stack[0]->isSameNode($node)) { $last = true; $node = $context; } /* 4. If node is a select element, then switch the insertion mode to "in select" and abort these steps. (fragment case) */ if($node->tagName === 'select') { $this->mode = self::IN_SELECT; break; /* 5. If node is a td or th element, then switch the insertion mode to "in cell" and abort these steps. */ } elseif($node->tagName === 'td' || $node->nodeName === 'th') { $this->mode = self::IN_CELL; break; /* 6. If node is a tr element, then switch the insertion mode to "in row" and abort these steps. */ } elseif($node->tagName === 'tr') { $this->mode = self::IN_ROW; break; /* 7. If node is a tbody, thead, or tfoot element, then switch the insertion mode to "in table body" and abort these steps. */ } elseif(in_array($node->tagName, array('tbody', 'thead', 'tfoot'))) { $this->mode = self::IN_TABLE_BODY; break; /* 8. If node is a caption element, then switch the insertion mode to "in caption" and abort these steps. */ } elseif($node->tagName === 'caption') { $this->mode = self::IN_CAPTION; break; /* 9. If node is a colgroup element, then switch the insertion mode to "in column group" and abort these steps. (innerHTML case) */ } elseif($node->tagName === 'colgroup') { $this->mode = self::IN_COLUMN_GROUP; break; /* 10. If node is a table element, then switch the insertion mode to "in table" and abort these steps. */ } elseif($node->tagName === 'table') { $this->mode = self::IN_TABLE; break; /* 11. If node is an element from the MathML namespace or the SVG * namespace, then switch the insertion mode to "in foreign * content", let the secondary insertion mode be "in body", and * abort these steps. */ } elseif($node->namespaceURI === self::NS_SVG || $node->namespaceURI === self::NS_MATHML) { $this->mode = self::IN_FOREIGN_CONTENT; $this->secondary_mode = self::IN_BODY; break; /* 12. If node is a head element, then switch the insertion mode to "in body" ("in body"! not "in head"!) and abort these steps. (fragment case) */ } elseif($node->tagName === 'head') { $this->mode = self::IN_BODY; break; /* 13. If node is a body element, then switch the insertion mode to "in body" and abort these steps. */ } elseif($node->tagName === 'body') { $this->mode = self::IN_BODY; break; /* 14. If node is a frameset element, then switch the insertion mode to "in frameset" and abort these steps. (fragment case) */ } elseif($node->tagName === 'frameset') { $this->mode = self::IN_FRAMESET; break; /* 15. If node is an html element, then: if the head element pointer is null, switch the insertion mode to "before head", otherwise, switch the insertion mode to "after head". In either case, abort these steps. (fragment case) */ } elseif($node->tagName === 'html') { $this->mode = ($this->head_pointer === null) ? self::BEFORE_HEAD : self::AFTER_HEAD; break; /* 16. If last is true, then set the insertion mode to "in body" and abort these steps. (fragment case) */ } elseif($last) { $this->mode = self::IN_BODY; break; } } } private function closeCell() { /* If the stack of open elements has a td or th element in table scope, then act as if an end tag token with that tag name had been seen. */ foreach(array('td', 'th') as $cell) { if($this->elementInScope($cell, true)) { $this->emitToken(array( 'name' => $cell, 'type' => Tokenizer::ENDTAG )); break; } } } private function processWithRulesFor($token, $mode) { /* "using the rules for the m insertion mode", where m is one of these * modes, the user agent must use the rules described under the m * insertion mode's section, but must leave the insertion mode * unchanged unless the rules in m themselves switch the insertion mode * to a new value. */ return $this->emitToken($token, $mode); } private function insertCDATAElement($token) { $this->insertElement($token); $this->original_mode = $this->mode; $this->mode = self::IN_CDATA_RCDATA; $this->content_model = Tokenizer::CDATA; } private function insertRCDATAElement($token) { $this->insertElement($token); $this->original_mode = $this->mode; $this->mode = self::IN_CDATA_RCDATA; $this->content_model = Tokenizer::RCDATA; } private function getAttr($token, $key) { if (!isset($token['attr'])) return false; $ret = false; foreach ($token['attr'] as $keypair) { if ($keypair['name'] === $key) $ret = $keypair['value']; } return $ret; } private function getCurrentTable() { /* The current table is the last table element in the stack of open * elements, if there is one. If there is no table element in the stack * of open elements (fragment case), then the current table is the * first element in the stack of open elements (the html element). */ for ($i = count($this->stack) - 1; $i >= 0; $i--) { if ($this->stack[$i]->tagName === 'table') { return $this->stack[$i]; } } return $this->stack[0]; } private function getFosterParent() { /* The foster parent element is the parent element of the last table element in the stack of open elements, if there is a table element and it has such a parent element. If there is no table element in the stack of open elements (innerHTML case), then the foster parent element is the first element in the stack of open elements (the html element). Otherwise, if there is a table element in the stack of open elements, but the last table element in the stack of open elements has no parent, or its parent node is not an element, then the foster parent element is the element before the last table element in the stack of open elements. */ for($n = count($this->stack) - 1; $n >= 0; $n--) { if($this->stack[$n]->tagName === 'table') { $table = $this->stack[$n]; break; } } if(isset($table) && $table->parentNode !== null) { return $table->parentNode; } elseif(!isset($table)) { return $this->stack[0]; } elseif(isset($table) && ($table->parentNode === null || $table->parentNode->nodeType !== XML_ELEMENT_NODE)) { return $this->stack[$n - 1]; } } public function fosterParent($node) { $foster_parent = $this->getFosterParent(); $table = $this->getCurrentTable(); // almost equivalent to last table element, except it can be html /* When a node node is to be foster parented, the node node must be * inserted into the foster parent element, and the current table must * be marked as tainted. (Once the current table has been tainted, * whitespace characters are inserted into the foster parent element * instead of the current node.) */ $table->tainted = true; /* If the foster parent element is the parent element of the last table * element in the stack of open elements, then node must be inserted * immediately before the last table element in the stack of open * elements in the foster parent element; otherwise, node must be * appended to the foster parent element. */ if ($table->tagName === 'table' && $table->parentNode->isSameNode($foster_parent)) { $foster_parent->insertBefore($node, $table); } else { $foster_parent->appendChild($node); } } /** * For debugging, prints the stack */ private function printStack() { $names = array(); foreach ($this->stack as $i => $element) { $names[] = $element->tagName; } echo " -> stack [" . implode(', ', $names) . "]\n"; } /** * For debugging, prints active formatting elements */ private function printActiveFormattingElements() { if (!$this->a_formatting) return; $names = array(); foreach ($this->a_formatting as $node) { if ($node === self::MARKER) $names[] = 'MARKER'; else $names[] = $node->tagName; } echo " -> active formatting [" . implode(', ', $names) . "]\n"; } public function currentTableIsTainted() { return !empty($this->getCurrentTable()->tainted); } /** * Sets up the tree constructor for building a fragment. */ public function setupContext($context = null) { $this->fragment = true; if ($context) { $context = $this->dom->createElementNS(self::NS_HTML, $context); /* 4.1. Set the HTML parser's tokenization stage's content model * flag according to the context element, as follows: */ switch ($context->tagName) { case 'title': case 'textarea': $this->content_model = Tokenizer::RCDATA; break; case 'style': case 'script': case 'xmp': case 'iframe': case 'noembed': case 'noframes': $this->content_model = Tokenizer::CDATA; break; case 'noscript': // XSCRIPT: assuming scripting is enabled $this->content_model = Tokenizer::CDATA; break; case 'plaintext': $this->content_model = Tokenizer::PLAINTEXT; break; } /* 4.2. Let root be a new html element with no attributes. */ $root = $this->dom->createElementNS(self::NS_HTML, 'html'); $this->root = $root; /* 4.3 Append the element root to the Document node created above. */ $this->dom->appendChild($root); /* 4.4 Set up the parser's stack of open elements so that it * contains just the single element root. */ $this->stack = array($root); /* 4.5 Reset the parser's insertion mode appropriately. */ $this->resetInsertionMode($context); /* 4.6 Set the parser's form element pointer to the nearest node * to the context element that is a form element (going straight up * the ancestor chain, and including the element itself, if it is a * form element), or, if there is no such form element, to null. */ $node = $context; do { if ($node->tagName === 'form') { $this->form_pointer = $node; break; } } while ($node = $node->parentNode); } } public function adjustMathMLAttributes($token) { foreach ($token['attr'] as &$kp) { if ($kp['name'] === 'definitionurl') { $kp['name'] = 'definitionURL'; } } return $token; } public function adjustSVGAttributes($token) { static $lookup = array( 'attributename' => 'attributeName', 'attributetype' => 'attributeType', 'basefrequency' => 'baseFrequency', 'baseprofile' => 'baseProfile', 'calcmode' => 'calcMode', 'clippathunits' => 'clipPathUnits', 'contentscripttype' => 'contentScriptType', 'contentstyletype' => 'contentStyleType', 'diffuseconstant' => 'diffuseConstant', 'edgemode' => 'edgeMode', 'externalresourcesrequired' => 'externalResourcesRequired', 'filterres' => 'filterRes', 'filterunits' => 'filterUnits', 'glyphref' => 'glyphRef', 'gradienttransform' => 'gradientTransform', 'gradientunits' => 'gradientUnits', 'kernelmatrix' => 'kernelMatrix', 'kernelunitlength' => 'kernelUnitLength', 'keypoints' => 'keyPoints', 'keysplines' => 'keySplines', 'keytimes' => 'keyTimes', 'lengthadjust' => 'lengthAdjust', 'limitingconeangle' => 'limitingConeAngle', 'markerheight' => 'markerHeight', 'markerunits' => 'markerUnits', 'markerwidth' => 'markerWidth', 'maskcontentunits' => 'maskContentUnits', 'maskunits' => 'maskUnits', 'numoctaves' => 'numOctaves', 'pathlength' => 'pathLength', 'patterncontentunits' => 'patternContentUnits', 'patterntransform' => 'patternTransform', 'patternunits' => 'patternUnits', 'pointsatx' => 'pointsAtX', 'pointsaty' => 'pointsAtY', 'pointsatz' => 'pointsAtZ', 'preservealpha' => 'preserveAlpha', 'preserveaspectratio' => 'preserveAspectRatio', 'primitiveunits' => 'primitiveUnits', 'refx' => 'refX', 'refy' => 'refY', 'repeatcount' => 'repeatCount', 'repeatdur' => 'repeatDur', 'requiredextensions' => 'requiredExtensions', 'requiredfeatures' => 'requiredFeatures', 'specularconstant' => 'specularConstant', 'specularexponent' => 'specularExponent', 'spreadmethod' => 'spreadMethod', 'startoffset' => 'startOffset', 'stddeviation' => 'stdDeviation', 'stitchtiles' => 'stitchTiles', 'surfacescale' => 'surfaceScale', 'systemlanguage' => 'systemLanguage', 'tablevalues' => 'tableValues', 'targetx' => 'targetX', 'targety' => 'targetY', 'textlength' => 'textLength', 'viewbox' => 'viewBox', 'viewtarget' => 'viewTarget', 'xchannelselector' => 'xChannelSelector', 'ychannelselector' => 'yChannelSelector', 'zoomandpan' => 'zoomAndPan', ); foreach ($token['attr'] as &$kp) { if (isset($lookup[$kp['name']])) { $kp['name'] = $lookup[$kp['name']]; } } return $token; } public function adjustForeignAttributes($token) { static $lookup = array( 'xlink:actuate' => array('xlink', 'actuate', self::NS_XLINK), 'xlink:arcrole' => array('xlink', 'arcrole', self::NS_XLINK), 'xlink:href' => array('xlink', 'href', self::NS_XLINK), 'xlink:role' => array('xlink', 'role', self::NS_XLINK), 'xlink:show' => array('xlink', 'show', self::NS_XLINK), 'xlink:title' => array('xlink', 'title', self::NS_XLINK), 'xlink:type' => array('xlink', 'type', self::NS_XLINK), 'xml:base' => array('xml', 'base', self::NS_XML), 'xml:lang' => array('xml', 'lang', self::NS_XML), 'xml:space' => array('xml', 'space', self::NS_XML), 'xmlns' => array(null, 'xmlns', self::NS_XMLNS), 'xmlns:xlink' => array('xmlns', 'xlink', self::NS_XMLNS), ); foreach ($token['attr'] as &$kp) { if (isset($lookup[$kp['name']])) { $kp['name'] = $lookup[$kp['name']]; } } return $token; } public function insertForeignElement($token, $namespaceURI) { $el = $this->dom->createElementNS($namespaceURI, $token['name']); if (!empty($token['attr'])) { foreach ($token['attr'] as $kp) { $attr = $kp['name']; if (is_array($attr)) { $ns = $attr[2]; $attr = $attr[1]; } else { $ns = self::NS_HTML; } if (!$el->hasAttributeNS($ns, $attr)) { // XSKETCHY: work around godawful libxml bug if ($ns === self::NS_XLINK) { $el->setAttribute('xlink:'.$attr, $kp['value']); } elseif ($ns === self::NS_HTML) { // Another godawful libxml bug $el->setAttribute($attr, $kp['value']); } else { $el->setAttributeNS($ns, $attr, $kp['value']); } } } } $this->appendToRealParent($el); $this->stack[] = $el; // XERROR: see below /* If the newly created element has an xmlns attribute in the XMLNS * namespace whose value is not exactly the same as the element's * namespace, that is a parse error. Similarly, if the newly created * element has an xmlns:xlink attribute in the XMLNS namespace whose * value is not the XLink Namespace, that is a parse error. */ } public function save() { $this->dom->normalize(); if (!$this->fragment) { return $this->dom; } else { if ($this->root) { return $this->root->childNodes; } else { return $this->dom->childNodes; } } } } PK e™fMŽ,² tests/all-tests.phpnu W+A„¶ data = json_decode(file_get_contents($this->filename)); } public function getDescription($test) { return $test->description; } public function getDataTests() { return isset($this->data->tests) ? $this->data->tests : array(); // could be a weird xmlViolationsTest } } PK e™fMÃfmØ Ø # tests/HTML5Lib/Tests/ParserTest.phpnu W+A„¶ '); $this->assertIsA($result, 'DOMDocument'); } public function testParseFragment() { $result = Parser::parseFragment('asdf foo'); $this->assertIsA($result, 'DOMNodeList'); } } PK e™fM ‡Ð Ð ( tests/HTML5Lib/Tests/TestDataHarness.phpnu W+A„¶ data = new TestData($this->filename); } public function getDescription($test) { return $test['data']; } public function getDataTests() { return $this->data->tests; } } PK e™fM\O”FX X * tests/HTML5Lib/Tests/TestableTokenizer.phpnu W+A„¶ _contentModelFlag = $contentModelFlag; $this->_lastStartFlag = $lastStartFlag; } public function parse() { $this->content_model = $this->_contentModelFlag; if ($this->_lastStartFlag) { $this->token = array( 'type' => self::STARTTAG, 'name' => $this->_lastStartFlag, ); } return parent::parse(); } // --end mismatched interface protected function emitToken($token, $checkStream = true) { if ($checkStream) { // Emit errors from input stream. while ($this->stream->errors) { $this->emitToken(array_shift($this->stream->errors), false); } } // tree handling code omitted switch ($token['type']) { case self::DOCTYPE: if (!isset($token['name'])) $token['name'] = null; if (!isset($token['public'])) $token['public'] = null; if (!isset($token['system'])) $token['system'] = null; $this->outputTokens[] = array('DOCTYPE', $token['name'], $token['public'], $token['system'], empty($token['force-quirks'])); break; case self::STARTTAG: $attr = new stdclass(); foreach ($token['attr'] as $keypair) { // XXX this is IMPORTANT behavior, check if it's // in TreeBuilder $name = $keypair['name']; if (isset($attr->$name)) continue; $attr->$name = $keypair['value']; } $start = array('StartTag', $token['name'], $attr); if (isset($token['self-closing'])) $start[] = true; $this->outputTokens[] = $start; break; case self::ENDTAG: $this->outputTokens[] = array('EndTag', $token['name']); // this is logic in the parent emitToken algorithm, but // for optimization reasons we haven't factored it out $this->content_model = self::PCDATA; break; case self::COMMENT: $this->outputTokens[] = array('Comment', $token['data']); break; case self::CHARACTER: case self::SPACECHARACTER: if (count($this->outputTokens)) { $old = array_pop($this->outputTokens); if ($old[0] === 'Character') { $old[1] .= $token['data']; $this->outputTokens[] = $old; break; } $this->outputTokens[] = $old; } $this->outputTokens[] = array('Character', $token['data']); break; case self::PARSEERROR: $this->outputTokens[] = 'ParseError'; break; } } } PK e™fM]if; ; . tests/HTML5Lib/Tests/TokenizerPositionTest.phpnu W+A„¶ characterTokens) { array_pop($this->outputLines); array_pop($this->outputCols); } $this->characterTokens[] = $token; default: $this->outputLines[] = $this->stream()->getCurrentLine(); $this->outputCols[] = $this->stream()->getColumnOffset(); } if ($token['type'] !== self::CHARACTER) { $this->characterTokens = array(); } } } class TokenizerTestOfPosition extends UnitTestCase { function testBasic() { $this->assertPositions( "f\na", array(1,1,1,1, 2,2,2,2), array(3,6,7,10,0,3,4,8) ); } function testUnicode() { $this->assertPositions( "\xC2\xA2\xE2\x82\xACa\xf4\x8a\xaf\x8d", array(1,1,1,1,1), array(1,4,6,9,10) ); } function testData() { $this->assertPositions( "a\na\n\xC2\xA2", array(3,3), array(1,4) ); } function testMarkupDeclarationDoubleDash() { $this->assertPositions( '', array(1), array(12) ); } function testMarkupDeclarationDoctype() { $this->assertPositions( '', array(1), array(10) ); } function testAfterDoctypeNamePublic() { $this->assertPositions( '', array(1), array(23) ); } function testAfterDoctypeNameSystem() { $this->assertPositions( '', array(1), array(23) ); } function testDecEntitySansSemicolon() { $this->assertPositions( 'Ĭ', array(1), array(5) ); } function testDecEntityWithSemicolon() { $this->assertPositions( 'Ĭ', array(1), array(6) ); } function testHexEntity() { $this->assertPositions( '̀', array(1), array(7) ); } function testEmptyEntity() { $this->assertPositions( '', array(1,1), array(3,6) ); } function testNamedEntity() { $this->assertPositions( '"foo', array(1,1), array(9,12) ); } function testBadNamedEntity() { $this->assertPositions( '&zzz;b', array(1), array(6) ); } function testAttributeEntity() { $this->assertPositions( 'a', array( 1, 1), array(16,17) ); } function testBogusComment() { $this->assertPositions( "d", array(2,2), array(5,6) ); } protected function assertPositions($input, $lines, $cols, $flag = Tokenizer::PCDATA, $lastStartTag = null) { $tokenizer = new PositionTestableTokenizer($input, $flag, $lastStartTag); $GLOBALS['TIME'] -= get_microtime(); $tokenizer->parse($input); $GLOBALS['TIME'] += get_microtime(); $this->assertIdentical($tokenizer->outputLines, $lines, 'Lines: %s'); $this->assertIdentical($tokenizer->outputCols, $cols, 'Cols: %s'); } } PK e™fMe~QaI I % tests/HTML5Lib/Tests/TestDataTest.phpnu W+A„¶ assertIdentical($data->tests, array( array('data' => "Foo\n", 'des' => "Bar\n"), array('data' => "Foo\n") )); } function testStrDom() { $dom = new DOMDocument(); $dom->loadHTML('foobarasdf'); $this->assertIdentical(TestData::strDom($dom), <<
| | | baz="1" | foo="bar" | "foo" | | "bar" | "asdf" RESULT ); } } PK e™fM§î‹`“/ “/ ( tests/HTML5Lib/Tests/InputStreamTest.phpnu W+A„¶ assertIdentical("\xEF\xBF\xBD", $stream->remainingChars(), $name); } public function testInvalidReplace() { // Above U+10FFFF $this->invalidReplaceTestHandler("\xF5\x90\x80\x80", 'U+110000'); // Incomplete $this->invalidReplaceTestHandler("\xDF", 'Incomplete two byte sequence (missing final byte)'); $this->invalidReplaceTestHandler("\xEF\xBF", 'Incomplete three byte sequence (missing final byte)'); $this->invalidReplaceTestHandler("\xF4\xBF\xBF", 'Incomplete four byte sequence (missing final byte)'); // Min/max continuation bytes $this->invalidReplaceTestHandler("\x80", 'Lone 80 continuation byte'); $this->invalidReplaceTestHandler("\xBF", 'Lone BF continuation byte'); // Invalid bytes (these can never occur) $this->invalidReplaceTestHandler("\xFE", 'Invalid FE byte'); $this->invalidReplaceTestHandler("\xFF", 'Invalid FF byte'); // Min/max overlong $this->invalidReplaceTestHandler("\xC0\x80", 'Overlong representation of U+0000'); $this->invalidReplaceTestHandler("\xE0\x80\x80", 'Overlong representation of U+0000'); $this->invalidReplaceTestHandler("\xF0\x80\x80\x80", 'Overlong representation of U+0000'); $this->invalidReplaceTestHandler("\xF8\x80\x80\x80\x80", 'Overlong representation of U+0000'); $this->invalidReplaceTestHandler("\xFC\x80\x80\x80\x80\x80", 'Overlong representation of U+0000'); $this->invalidReplaceTestHandler("\xC1\xBF", 'Overlong representation of U+007F'); $this->invalidReplaceTestHandler("\xE0\x9F\xBF", 'Overlong representation of U+07FF'); $this->invalidReplaceTestHandler("\xF0\x8F\xBF\xBF", 'Overlong representation of U+FFFF'); } public function testStripLeadingBOM() { $leading = new InputStream("\xEF\xBB\xBFa"); $this->assertIdentical('a', $leading->char(), 'BOM should be stripped'); } public function testZWNBSP() { $stream = new InputStream("a\xEF\xBB\xBF"); $this->assertIdentical("a\xEF\xBB\xBF", $stream->remainingChars(), 'A non-leading U+FEFF (BOM/ZWNBSP) should remain'); } public function testNull() { $stream = new InputStream("\0\0\0"); $this->assertIdentical("\xEF\xBF\xBD\xEF\xBF\xBD\xEF\xBF\xBD", $stream->remainingChars(), 'Null character should be replaced by U+FFFD'); $this->assertIdentical(3, count($stream->errors), 'Null character should be throw parse error'); } public function testCRLF() { $stream = new InputStream("\r\n"); $this->assertIdentical("\n", $stream->remainingChars(), 'CRLF should be replaced by LF'); } public function testCR() { $stream = new InputStream("\r"); $this->assertIdentical("\n", $stream->remainingChars(), 'CR should be replaced by LF'); } public function invalidParseErrorTestHandler($input, $numErrors, $name) { $stream = new InputStream($input); $this->assertIdentical($input, $stream->remainingChars(), $name . ' (stream content)'); $this->assertIdentical($numErrors, count($stream->errors), $name . ' (number of errors)'); } public function testInvalidParseError() { // C0 controls (except U+0000 and U+000D due to different handling) $this->invalidParseErrorTestHandler("\x01", 1, 'U+0001 (C0 control)'); $this->invalidParseErrorTestHandler("\x02", 1, 'U+0002 (C0 control)'); $this->invalidParseErrorTestHandler("\x03", 1, 'U+0003 (C0 control)'); $this->invalidParseErrorTestHandler("\x04", 1, 'U+0004 (C0 control)'); $this->invalidParseErrorTestHandler("\x05", 1, 'U+0005 (C0 control)'); $this->invalidParseErrorTestHandler("\x06", 1, 'U+0006 (C0 control)'); $this->invalidParseErrorTestHandler("\x07", 1, 'U+0007 (C0 control)'); $this->invalidParseErrorTestHandler("\x08", 1, 'U+0008 (C0 control)'); $this->invalidParseErrorTestHandler("\x09", 0, 'U+0009 (C0 control)'); $this->invalidParseErrorTestHandler("\x0A", 0, 'U+000A (C0 control)'); $this->invalidParseErrorTestHandler("\x0B", 1, 'U+000B (C0 control)'); $this->invalidParseErrorTestHandler("\x0C", 0, 'U+000C (C0 control)'); $this->invalidParseErrorTestHandler("\x0E", 1, 'U+000E (C0 control)'); $this->invalidParseErrorTestHandler("\x0F", 1, 'U+000F (C0 control)'); $this->invalidParseErrorTestHandler("\x10", 1, 'U+0010 (C0 control)'); $this->invalidParseErrorTestHandler("\x11", 1, 'U+0011 (C0 control)'); $this->invalidParseErrorTestHandler("\x12", 1, 'U+0012 (C0 control)'); $this->invalidParseErrorTestHandler("\x13", 1, 'U+0013 (C0 control)'); $this->invalidParseErrorTestHandler("\x14", 1, 'U+0014 (C0 control)'); $this->invalidParseErrorTestHandler("\x15", 1, 'U+0015 (C0 control)'); $this->invalidParseErrorTestHandler("\x16", 1, 'U+0016 (C0 control)'); $this->invalidParseErrorTestHandler("\x17", 1, 'U+0017 (C0 control)'); $this->invalidParseErrorTestHandler("\x18", 1, 'U+0018 (C0 control)'); $this->invalidParseErrorTestHandler("\x19", 1, 'U+0019 (C0 control)'); $this->invalidParseErrorTestHandler("\x1A", 1, 'U+001A (C0 control)'); $this->invalidParseErrorTestHandler("\x1B", 1, 'U+001B (C0 control)'); $this->invalidParseErrorTestHandler("\x1C", 1, 'U+001C (C0 control)'); $this->invalidParseErrorTestHandler("\x1D", 1, 'U+001D (C0 control)'); $this->invalidParseErrorTestHandler("\x1E", 1, 'U+001E (C0 control)'); $this->invalidParseErrorTestHandler("\x1F", 1, 'U+001F (C0 control)'); // DEL (U+007F) $this->invalidParseErrorTestHandler("\x7F", 1, 'U+007F'); // C1 Controls $this->invalidParseErrorTestHandler("\xC2\x80", 1, 'U+0080 (C1 control)'); $this->invalidParseErrorTestHandler("\xC2\x9F", 1, 'U+009F (C1 control)'); $this->invalidParseErrorTestHandler("\xC2\xA0", 0, 'U+00A0 (first codepoint above highest C1 control)'); // Single UTF-16 surrogates $this->invalidParseErrorTestHandler("\xED\xA0\x80", 1, 'U+D800 (UTF-16 surrogate character)'); $this->invalidParseErrorTestHandler("\xED\xAD\xBF", 1, 'U+DB7F (UTF-16 surrogate character)'); $this->invalidParseErrorTestHandler("\xED\xAE\x80", 1, 'U+DB80 (UTF-16 surrogate character)'); $this->invalidParseErrorTestHandler("\xED\xAF\xBF", 1, 'U+DBFF (UTF-16 surrogate character)'); $this->invalidParseErrorTestHandler("\xED\xB0\x80", 1, 'U+DC00 (UTF-16 surrogate character)'); $this->invalidParseErrorTestHandler("\xED\xBE\x80", 1, 'U+DF80 (UTF-16 surrogate character)'); $this->invalidParseErrorTestHandler("\xED\xBF\xBF", 1, 'U+DFFF (UTF-16 surrogate character)'); // Paired UTF-16 surrogates $this->invalidParseErrorTestHandler("\xED\xA0\x80\xED\xB0\x80", 2, 'U+D800 U+DC00 (paired UTF-16 surrogates)'); $this->invalidParseErrorTestHandler("\xED\xA0\x80\xED\xBF\xBF", 2, 'U+D800 U+DFFF (paired UTF-16 surrogates)'); $this->invalidParseErrorTestHandler("\xED\xAD\xBF\xED\xB0\x80", 2, 'U+DB7F U+DC00 (paired UTF-16 surrogates)'); $this->invalidParseErrorTestHandler("\xED\xAD\xBF\xED\xBF\xBF", 2, 'U+DB7F U+DFFF (paired UTF-16 surrogates)'); $this->invalidParseErrorTestHandler("\xED\xAE\x80\xED\xB0\x80", 2, 'U+DB80 U+DC00 (paired UTF-16 surrogates)'); $this->invalidParseErrorTestHandler("\xED\xAE\x80\xED\xBF\xBF", 2, 'U+DB80 U+DFFF (paired UTF-16 surrogates)'); $this->invalidParseErrorTestHandler("\xED\xAF\xBF\xED\xB0\x80", 2, 'U+DBFF U+DC00 (paired UTF-16 surrogates)'); $this->invalidParseErrorTestHandler("\xED\xAF\xBF\xED\xBF\xBF", 2, 'U+DBFF U+DFFF (paired UTF-16 surrogates)'); // Charcters surrounding surrogates $this->invalidParseErrorTestHandler("\xED\x9F\xBF", 0, 'U+D7FF (one codepoint below lowest surrogate codepoint)'); $this->invalidParseErrorTestHandler("\xEF\xBF\xBD", 0, 'U+DE00 (one codepoint above highest surrogate codepoint)'); // Permanent noncharacters $this->invalidParseErrorTestHandler("\xEF\xB7\x90", 1, 'U+FDD0 (permanent noncharacter)'); $this->invalidParseErrorTestHandler("\xEF\xB7\xAF", 1, 'U+FDEF (permanent noncharacter)'); $this->invalidParseErrorTestHandler("\xEF\xBF\xBE", 1, 'U+FFFE (permanent noncharacter)'); $this->invalidParseErrorTestHandler("\xEF\xBF\xBF", 1, 'U+FFFF (permanent noncharacter)'); $this->invalidParseErrorTestHandler("\xF0\x9F\xBF\xBE", 1, 'U+1FFFE (permanent noncharacter)'); $this->invalidParseErrorTestHandler("\xF0\x9F\xBF\xBF", 1, 'U+1FFFF (permanent noncharacter)'); $this->invalidParseErrorTestHandler("\xF0\xAF\xBF\xBE", 1, 'U+2FFFE (permanent noncharacter)'); $this->invalidParseErrorTestHandler("\xF0\xAF\xBF\xBF", 1, 'U+2FFFF (permanent noncharacter)'); $this->invalidParseErrorTestHandler("\xF0\xBF\xBF\xBE", 1, 'U+3FFFE (permanent noncharacter)'); $this->invalidParseErrorTestHandler("\xF0\xBF\xBF\xBF", 1, 'U+3FFFF (permanent noncharacter)'); $this->invalidParseErrorTestHandler("\xF1\x8F\xBF\xBE", 1, 'U+4FFFE (permanent noncharacter)'); $this->invalidParseErrorTestHandler("\xF1\x8F\xBF\xBF", 1, 'U+4FFFF (permanent noncharacter)'); $this->invalidParseErrorTestHandler("\xF1\x9F\xBF\xBE", 1, 'U+5FFFE (permanent noncharacter)'); $this->invalidParseErrorTestHandler("\xF1\x9F\xBF\xBF", 1, 'U+5FFFF (permanent noncharacter)'); $this->invalidParseErrorTestHandler("\xF1\xAF\xBF\xBE", 1, 'U+6FFFE (permanent noncharacter)'); $this->invalidParseErrorTestHandler("\xF1\xAF\xBF\xBF", 1, 'U+6FFFF (permanent noncharacter)'); $this->invalidParseErrorTestHandler("\xF1\xBF\xBF\xBE", 1, 'U+7FFFE (permanent noncharacter)'); $this->invalidParseErrorTestHandler("\xF1\xBF\xBF\xBF", 1, 'U+7FFFF (permanent noncharacter)'); $this->invalidParseErrorTestHandler("\xF2\x8F\xBF\xBE", 1, 'U+8FFFE (permanent noncharacter)'); $this->invalidParseErrorTestHandler("\xF2\x8F\xBF\xBF", 1, 'U+8FFFF (permanent noncharacter)'); $this->invalidParseErrorTestHandler("\xF2\x9F\xBF\xBE", 1, 'U+9FFFE (permanent noncharacter)'); $this->invalidParseErrorTestHandler("\xF2\x9F\xBF\xBF", 1, 'U+9FFFF (permanent noncharacter)'); $this->invalidParseErrorTestHandler("\xF2\xAF\xBF\xBE", 1, 'U+AFFFE (permanent noncharacter)'); $this->invalidParseErrorTestHandler("\xF2\xAF\xBF\xBF", 1, 'U+AFFFF (permanent noncharacter)'); $this->invalidParseErrorTestHandler("\xF2\xBF\xBF\xBE", 1, 'U+BFFFE (permanent noncharacter)'); $this->invalidParseErrorTestHandler("\xF2\xBF\xBF\xBF", 1, 'U+BFFFF (permanent noncharacter)'); $this->invalidParseErrorTestHandler("\xF3\x8F\xBF\xBE", 1, 'U+CFFFE (permanent noncharacter)'); $this->invalidParseErrorTestHandler("\xF3\x8F\xBF\xBF", 1, 'U+CFFFF (permanent noncharacter)'); $this->invalidParseErrorTestHandler("\xF3\x9F\xBF\xBE", 1, 'U+DFFFE (permanent noncharacter)'); $this->invalidParseErrorTestHandler("\xF3\x9F\xBF\xBF", 1, 'U+DFFFF (permanent noncharacter)'); $this->invalidParseErrorTestHandler("\xF3\xAF\xBF\xBE", 1, 'U+EFFFE (permanent noncharacter)'); $this->invalidParseErrorTestHandler("\xF3\xAF\xBF\xBF", 1, 'U+EFFFF (permanent noncharacter)'); $this->invalidParseErrorTestHandler("\xF3\xBF\xBF\xBE", 1, 'U+FFFFE (permanent noncharacter)'); $this->invalidParseErrorTestHandler("\xF3\xBF\xBF\xBF", 1, 'U+FFFFF (permanent noncharacter)'); $this->invalidParseErrorTestHandler("\xF4\x8F\xBF\xBE", 1, 'U+10FFFE (permanent noncharacter)'); $this->invalidParseErrorTestHandler("\xF4\x8F\xBF\xBF", 1, 'U+10FFFF (permanent noncharacter)'); } } PK e™fM‡z×úè è &