Файл: vendor/masterminds/html5/src/HTML5/Parser/Tokenizer.php
Строк: 1361
<?php
namespace MastermindsHTML5Parser;
use MastermindsHTML5Elements;
/**
* The HTML5 tokenizer.
*
* The tokenizer's role is reading data from the scanner and gathering it into
* semantic units. From the tokenizer, data is emitted to an event handler,
* which may (for example) create a DOM tree.
*
* The HTML5 specification has a detailed explanation of tokenizing HTML5. We
* follow that specification to the maximum extent that we can. If you find
* a discrepancy that is not documented, please file a bug and/or submit a
* patch.
*
* This tokenizer is implemented as a recursive descent parser.
*
* Within the API documentation, you may see references to the specific section
* of the HTML5 spec that the code attempts to reproduce. Example: 8.2.4.1.
* This refers to section 8.2.4.1 of the HTML5 CR specification.
*
* @see http://www.w3.org/TR/2012/CR-html5-20121217/
*/
class Tokenizer
{
protected $scanner;
protected $events;
protected $tok;
/**
* Buffer for text.
*/
protected $text = '';
// When this goes to false, the parser stops.
protected $carryOn = true;
protected $textMode = 0; // TEXTMODE_NORMAL;
protected $untilTag = null;
const CONFORMANT_XML = 'xml';
const CONFORMANT_HTML = 'html';
protected $mode = self::CONFORMANT_HTML;
/**
* Create a new tokenizer.
*
* Typically, parsing a document involves creating a new tokenizer, giving
* it a scanner (input) and an event handler (output), and then calling
* the Tokenizer::parse() method.`
*
* @param Scanner $scanner A scanner initialized with an input stream.
* @param EventHandler $eventHandler An event handler, initialized and ready to receive events.
* @param string $mode
*/
public function __construct($scanner, $eventHandler, $mode = self::CONFORMANT_HTML)
{
$this->scanner = $scanner;
$this->events = $eventHandler;
$this->mode = $mode;
}
/**
* Begin parsing.
*
* This will begin scanning the document, tokenizing as it goes.
* Tokens are emitted into the event handler.
*
* Tokenizing will continue until the document is completely
* read. Errors are emitted into the event handler, but
* the parser will attempt to continue parsing until the
* entire input stream is read.
*/
public function parse()
{
do {
$this->consumeData();
// FIXME: Add infinite loop protection.
} while ($this->carryOn);
}
/**
* Set the text mode for the character data reader.
*
* HTML5 defines three different modes for reading text:
* - Normal: Read until a tag is encountered.
* - RCDATA: Read until a tag is encountered, but skip a few otherwise-
* special characters.
* - Raw: Read until a special closing tag is encountered (viz. pre, script)
*
* This allows those modes to be set.
*
* Normally, setting is done by the event handler via a special return code on
* startTag(), but it can also be set manually using this function.
*
* @param int $textmode One of Elements::TEXT_*.
* @param string $untilTag The tag that should stop RAW or RCDATA mode. Normal mode does not
* use this indicator.
*/
public function setTextMode($textmode, $untilTag = null)
{
$this->textMode = $textmode & (Elements::TEXT_RAW | Elements::TEXT_RCDATA);
$this->untilTag = $untilTag;
}
/**
* Consume a character and make a move.
* HTML5 8.2.4.1.
*/
protected function consumeData()
{
$tok = $this->scanner->current();
if ('&' === $tok) {
// Character reference
$ref = $this->decodeCharacterReference();
$this->buffer($ref);
$tok = $this->scanner->current();
}
// Parse tag
if ('<' === $tok) {
// Any buffered text data can go out now.
$this->flushBuffer();
$tok = $this->scanner->next();
if (false === $tok) {
// end of string
$this->parseError('Illegal tag opening');
} elseif ('!' === $tok) {
$this->markupDeclaration();
} elseif ('/' === $tok) {
$this->endTag();
} elseif ('?' === $tok) {
$this->processingInstruction();
} elseif ($this->is_alpha($tok)) {
$this->tagName();
} else {
$this->parseError('Illegal tag opening');
// TODO is this necessary ?
$this->characterData();
}
$tok = $this->scanner->current();
}
if (false === $tok) {
// Handle end of document
$this->eof();
} else {
// Parse character
switch ($this->textMode) {
case Elements::TEXT_RAW:
$this->rawText($tok);
break;
case Elements::TEXT_RCDATA:
$this->rcdata($tok);
break;
default:
if ('<' === $tok || '&' === $tok) {
break;
}
// NULL character
if (" 0" === $tok) {
$this->parseError('Received null character.');
$this->text .= $tok;
$this->scanner->consume();
break;
}
$this->text .= $this->scanner->charsUntil("<&