X-Git-Url: https://scripts.mit.edu/gitweb/autoinstallsdev/mediawiki.git/blobdiff_plain/19e297c21b10b1b8a3acad5e73fc71dcb35db44a..6932310fd58ebef145fa01eb76edf7150284d8ea:/vendor/wikimedia/remex-html/RemexHtml/TreeBuilder/Dispatcher.php diff --git a/vendor/wikimedia/remex-html/RemexHtml/TreeBuilder/Dispatcher.php b/vendor/wikimedia/remex-html/RemexHtml/TreeBuilder/Dispatcher.php new file mode 100644 index 00000000..e6ecf1bb --- /dev/null +++ b/vendor/wikimedia/remex-html/RemexHtml/TreeBuilder/Dispatcher.php @@ -0,0 +1,417 @@ + Initial::class, + self::BEFORE_HTML => BeforeHtml::class, + self::BEFORE_HEAD => BeforeHead::class, + self::IN_HEAD => InHead::class, + self::IN_HEAD_NOSCRIPT => InHeadNoscript::class, + self::AFTER_HEAD => AfterHead::class, + self::IN_BODY => InBody::class, + self::TEXT => Text::class, + self::IN_TABLE => InTable::class, + self::IN_TABLE_TEXT => InTableText::class, + self::IN_CAPTION => InCaption::class, + self::IN_COLUMN_GROUP => InColumnGroup::class, + self::IN_TABLE_BODY => InTableBody::class, + self::IN_ROW => InRow::class, + self::IN_CELL => InCell::class, + self::IN_SELECT => InSelect::class, + self::IN_SELECT_IN_TABLE => InSelectInTable::class, + self::IN_TEMPLATE => InTemplate::class, + self::AFTER_BODY => AfterBody::class, + self::IN_FRAMESET => InFrameset::class, + self::AFTER_FRAMESET => AfterFrameset::class, + self::AFTER_AFTER_BODY => AfterAfterBody::class, + self::AFTER_AFTER_FRAMESET => AfterAfterFrameset::class, + self::IN_FOREIGN_CONTENT => InForeignContent::class, + self::IN_PRE => InPre::class, + self::IN_TEXTAREA => InTextarea::class, + ]; + + // Public shortcuts for "using the rules for" actions + public $inHead; + public $inBody; + public $inTable; + public $inSelect; + public $inTemplate; + public $inForeign; + + /// @var TreeBuilder + protected $builder; + + /** + * The InsertionMode object for the current insertion mode in HTML content + */ + protected $handler; + + /** + * An array mapping insertion mode indexes to InsertionMode objects + */ + protected $dispatchTable; + + /** + * The insertion mode index + */ + protected $mode; + + /** + * The "original insertion mode" index + */ + protected $originalMode; + + /** + * The insertion mode sets this to true to acknowledge the tag's + * self-closing flag. + */ + public $ack; + + /** + * The stack of template insertion modes + * @var TemplateModeStack + */ + public $templateModeStack; + + /** + * @param TreeBuilder $builder + */ + public function __construct( TreeBuilder $builder ) { + $this->builder = $builder; + $this->templateModeStack = new TemplateModeStack; + } + + /** + * Switch the insertion mode, and return the new handler + * + * @param integer $mode + * @return InsertionMode + */ + public function switchMode( $mode ) { + $this->mode = $mode; + return $this->handler = $this->dispatchTable[$mode]; + } + + /** + * Let the original insertion mode be the current insertion mode, and + * switch the insertion mode to some new value. Return the new handler. + * + * @param integer $mode + * @return InsertionMode + */ + public function switchAndSave( $mode ) { + $this->originalMode = $this->mode; + $this->mode = $mode; + return $this->handler = $this->dispatchTable[$mode]; + } + + /** + * Switch the insertion mode to the original insertion mode and return the + * new handler. + * + * @return InsertionMode + */ + public function restoreMode() { + if ( $this->originalMode === null ) { + throw new TreeBuilderError( "original insertion mode is not set" ); + } + $mode = $this->mode = $this->originalMode; + $this->originalMode = null; + return $this->handler = $this->dispatchTable[$mode]; + } + + /** + * Get the handler for the current insertion mode in HTML content. + * This is used by the "in foreign" handler to execute the HTML insertion + * mode. It does not necessarily correspond to the handler currently being + * executed. + * + * @return InsertionMode + */ + public function getHandler() { + return $this->handler; + } + + /** + * True if we are in a table mode, for the purposes of switching to + * IN_SELECT_IN_TABLE as opposed to IN_SELECT. + * + * @return bool + */ + public function isInTableMode() { + static $tableModes = [ + self::IN_TABLE => true, + self::IN_CAPTION => true, + self::IN_TABLE_BODY => true, + self::IN_ROW => true, + self::IN_CELL => true ]; + return isset( $tableModes[$this->mode] ); + } + + /** + * Reset the insertion mode appropriately, and return the new handler. + * + * @return InsertionMode + */ + public function reset() { + return $this->switchMode( $this->getAppropriateMode() ); + } + + /** + * Get the insertion mode index which is switched to when we reset the + * insertion mode appropriately. + * + * @return integer + */ + protected function getAppropriateMode() { + $builder = $this->builder; + $stack = $builder->stack; + $last = false; + $node = $stack->current; + for ( $idx = $stack->length() - 1; $idx >= 0; $idx-- ) { + $node = $stack->item( $idx ); + if ( $idx === 0 ) { + $last = true; + if ( $builder->isFragment ) { + $node = $builder->fragmentContext; + } + } + + switch ( $node->htmlName ) { + case 'select': + if ( $last ) { + return self::IN_SELECT; + } + for ( $ancestorIdx = $idx - 1; $ancestorIdx >= 1; $ancestorIdx-- ) { + $ancestor = $stack->item( $ancestorIdx ); + if ( $ancestor->htmlName === 'template' ) { + return self::IN_SELECT; + } elseif ( $ancestor->htmlName === 'table' ) { + return self::IN_SELECT_IN_TABLE; + } + } + return self::IN_SELECT; + + case 'td': + case 'th': + if ( !$last ) { + return self::IN_CELL; + } + break; + + case 'tr': + return self::IN_ROW; + + case 'tbody': + case 'thead': + case 'tfoot': + return self::IN_TABLE_BODY; + + case 'caption': + return self::IN_CAPTION; + + case 'colgroup': + return self::IN_COLUMN_GROUP; + + case 'table': + return self::IN_TABLE; + + case 'template': + return $this->templateModeStack->current; + + case 'head': + if ( $last ) { + return self::IN_BODY; + } else { + return self::IN_HEAD; + } + + case 'body': + return self::IN_BODY; + + case 'frameset': + return self::IN_FRAMESET; + + case 'html': + if ( $builder->headElement === null ) { + return self::BEFORE_HEAD; + } else { + return self::AFTER_HEAD; + } + } + } + + return self::IN_BODY; + } + + /** + * If the stack of open elements is empty, return null, otherwise return + * the adjusted current node. + */ + protected function dispatcherCurrentNode() { + $current = $this->builder->stack->current; + if ( $current && $current->stackIndex === 0 && $this->builder->isFragment ) { + return $this->builder->fragmentContext; + } else { + return $current; + } + } + + public function startDocument( Tokenizer $tokenizer, $namespace, $name ) { + $this->dispatchTable = []; + foreach ( self::$handlerClasses as $mode => $class ) { + $this->dispatchTable[$mode] = new $class( $this->builder, $this ); + } + + $this->inHead = $this->dispatchTable[self::IN_HEAD]; + $this->inBody = $this->dispatchTable[self::IN_BODY]; + $this->inTable = $this->dispatchTable[self::IN_TABLE]; + $this->inSelect = $this->dispatchTable[self::IN_SELECT]; + $this->inTemplate = $this->dispatchTable[self::IN_TEMPLATE]; + $this->inForeign = $this->dispatchTable[self::IN_FOREIGN_CONTENT]; + + $this->switchMode( self::INITIAL ); + + $this->builder->startDocument( $tokenizer, $namespace, $name ); + if ( $namespace !== null ) { + if ( $namespace === HTMLData::NS_HTML && $name === 'template' ) { + $this->templateModeStack->push( self::IN_TEMPLATE ); + } + $this->reset(); + } + } + + public function endDocument( $pos ) { + $this->handler->endDocument( $pos ); + + // All references to insertion modes must be explicitly released, since + // they have a circular reference back to $this + $this->dispatchTable = []; + $this->handler = null; + $this->inHead = null; + $this->inBody = null; + $this->inTable = null; + $this->inSelect = null; + $this->inTemplate = null; + $this->inForeign = null; + } + + public function error( $text, $pos ) { + $this->builder->error( $text, $pos ); + } + + public function characters( $text, $start, $length, $sourceStart, $sourceLength ) { + $current = $this->dispatcherCurrentNode(); + if ( !$current + || $current->namespace === HTMLData::NS_HTML + || $current->isMathmlTextIntegration() + || $current->isHtmlIntegration() + ) { + $this->handler->characters( $text, $start, $length, $sourceStart, $sourceLength ); + } else { + $this->inForeign->characters( + $text, $start, $length, $sourceStart, $sourceLength ); + } + } + + public function startTag( $name, Attributes $attrs, $selfClose, $sourceStart, $sourceLength ) { + $this->ack = false; + $current = $this->dispatcherCurrentNode(); + if ( !$current + || $current->namespace === HTMLData::NS_HTML + || ( $current->isMathmlTextIntegration() + && $name !== 'mglyph' + && $name !== 'malignmark' + ) + || ( $name === 'svg' + && $current->namespace === HTMLData::NS_MATHML + && $current->name === 'annotation-xml' + ) + || $current->isHtmlIntegration() + ) { + $this->handler->startTag( $name, $attrs, $selfClose, $sourceStart, $sourceLength ); + } else { + $this->inForeign->startTag( $name, $attrs, $selfClose, $sourceStart, $sourceLength ); + } + if ( $selfClose && !$this->ack ) { + $this->builder->error( "unacknowledged self-closing tag", $sourceStart ); + } + } + + public function endTag( $name, $sourceStart, $sourceLength ) { + $current = $this->dispatcherCurrentNode(); + if ( !$current || $current->namespace === HTMLData::NS_HTML ) { + $this->handler->endTag( $name, $sourceStart, $sourceLength ); + } else { + $this->inForeign->endTag( $name, $sourceStart, $sourceLength ); + } + } + + public function doctype( $name, $public, $system, $quirks, $sourceStart, $sourceLength ) { + $current = $this->dispatcherCurrentNode(); + if ( !$current || $current->namespace === HTMLData::NS_HTML ) { + $this->handler->doctype( $name, $public, $system, $quirks, + $sourceStart, $sourceLength ); + } else { + $this->inForeign->doctype( $name, $public, $system, $quirks, + $sourceStart, $sourceLength ); + } + } + + public function comment( $text, $sourceStart, $sourceLength ) { + $current = $this->dispatcherCurrentNode(); + if ( !$current || $current->namespace === HTMLData::NS_HTML ) { + $this->handler->comment( $text, $sourceStart, $sourceLength ); + } else { + $this->inForeign->comment( $text, $sourceStart, $sourceLength ); + } + } +}