Page MenuHomeWickedGov Phorge

TexArray.php
No OneTemporary

Size
15 KB
Referenced Files
None
Subscribers
None

TexArray.php

<?php
declare( strict_types = 1 );
namespace MediaWiki\Extension\Math\WikiTexVC\Nodes;
use Generator;
use InvalidArgumentException;
use MediaWiki\Extension\Math\WikiTexVC\MMLmappings\Util\MMLParsingUtil;
use MediaWiki\Extension\Math\WikiTexVC\MMLmappings\Util\MMLutil;
use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmo;
use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmrow;
use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmstyle;
use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmsup;
use MediaWiki\Extension\Math\WikiTexVC\TexUtil;
/**
*
*/
class TexArray extends TexNode implements \ArrayAccess, \IteratorAggregate {
protected bool $curly = false;
private ?LengthSpec $rowSpecs = null;
/**
* @param TexNode|string ...$args
* @return self
*/
public static function newCurly( ...$args ) {
$node = new self( ...$args );
$node->curly = true;
return $node;
}
/** @inheritDoc */
public function __construct( ...$args ) {
$nargs = [];
foreach ( $args as &$arg ) {
if ( $arg !== null ) {
array_push( $nargs, $arg );
}
}
self::checkInput( $nargs );
parent::__construct( ...$nargs );
}
public function checkForStyleArgs( TexNode $node ): ?array {
if ( $node instanceof Literal ) {
$name = trim( $node->getArg() );
switch ( $name ) {
case "\\displaystyle":
return [ "displaystyle" => "true", "scriptlevel" => "0" ];
case "\\scriptstyle":
return [ "displaystyle" => "false", "scriptlevel" => "1" ];
case "\\scriptscriptstyle":
return [ "displaystyle" => "false", "scriptlevel" => "2" ];
case "\\textstyle":
return [ "displaystyle" => "false", "scriptlevel" => "0" ];
}
}
return null;
}
/**
* Checks if an TexNode of Literal contains color information (color, pagecolor)
* and returns info how to continue with the parsing.
* @param TexNode $node node to check if it contains color info
* @return array index 0: (bool) was color element found, index 1: (string) specified color
*/
public function checkForColor( TexNode $node ) {
if ( $node instanceof Literal ) {
$name = trim( $node->getArg() );
if ( str_contains( $name, "\\color" ) ) {
$foundOperatorContent = MMLutil::initalParseLiteralExpression( $node->getArg() );
if ( !$foundOperatorContent ) {
// discarding color elements which not specify color
return [ true, null ];
} else {
return [ true, $foundOperatorContent[2][0] ];
}
} elseif ( str_contains( $name, "\\pagecolor" ) ) {
return [ true, null ];
}
}
return [ false, null ];
}
public function checkForColorDefinition( TexNode $node ): ?array {
if ( $node instanceof Literal ) {
$name = trim( $node->getArg() );
if ( str_contains( $name, "\\definecolor" ) ) {
return MMLParsingUtil::parseDefineColorExpression( $node->getArg() );
}
}
return null;
}
/**
* Checks two sequential nodes in TexArray if they contain information on sideset expressions.
* @param TexNode $currentNode first node in array to check (for sideset expression)
* @param TexNode|null $nextNode second node in array to check (for succeeding operator)
* @return TexNode|null the succeeding operator for further Parsing or null if sideset not found or invalid
*/
public function checkForSideset( TexNode $currentNode, ?TexNode $nextNode ): ?TexNode {
if ( !( $currentNode instanceof Fun2nb && $currentNode->getFname() == "\\sideset" ) ) {
return null;
}
if ( $nextNode instanceof Literal ||
$nextNode instanceof DQ ||
$nextNode instanceof UQ ||
$nextNode instanceof FQ ) {
return $nextNode;
}
return null;
}
public function checkForLimits( TexNode $currentNode, ?TexNode $nextNode ): array {
// Preceding 'lim' in example: "\\lim_{x \\to 2}"
if ( ( $currentNode instanceof DQ || $currentNode instanceof FQ )
&& $currentNode->containsFunc( "\\lim" ) ) {
if ( $currentNode->getBase() instanceof TexArray ) {
return [ $currentNode->getBase()->getArgs()[0], false ];
} else {
return [ $currentNode->getBase(), false ];
}
}
/** Find cases which have preceding Literals with nullary_macro-type operators i.e.:
* "\iint\limits_D \, dx\,dy"
*/
$tu = TexUtil::getInstance();
// Check whether the current node is a possible preceding literal
if ( !(
// logically superfluous brackets were inserted to improve readability
( $currentNode instanceof Literal &&
// Check if the current node is a nullary macro such as \iint, \sum, \prod, etc.
( $tu->nullary_macro( trim( $currentNode->getArg() ) )
// or a limit operator
|| ( trim( $currentNode->getArg() ) == "\\lim" ) ) ) ||
// or the special case of \operatorname
( $currentNode instanceof Fun1nb && $currentNode->getFname() == "\\operatorname" )
) ) {
return [ null, false ];
}
// Check whether the next node is a possible limits construct
if ( !( ( $nextNode instanceof DQ || $nextNode instanceof FQ )
&& $nextNode->getBase() instanceof Literal
&& ( $nextNode->containsFunc( "\\limits" ) || $nextNode->containsFunc( "\\nolimits" ) )
) ) {
return [ null, false ];
}
return [ $currentNode, true ];
}
public function checkForNot( TexNode $currentNode ): bool {
if ( $currentNode instanceof Literal && trim( $currentNode->getArg() ) == "\\not" ) {
return true;
}
return false;
}
public function checkForDerivatives( int $iStart, array $args ): int {
$ctr = 0;
for ( $i = $iStart, $count = count( $this->args ); $i < $count; $i++ ) {
$followUp = $args[$i];
if ( $followUp instanceof Literal && $followUp->getArg() === "'" ) {
$ctr++;
} else {
break;
}
}
return $ctr;
}
public function checkForNamedFctArgs( TexNode $currentNode, ?TexNode $nextNode ): array {
// Check if current node is named function
$hasNamedFct = false;
if ( $currentNode instanceof TexArray && count( $currentNode->args ) == 2 ) {
$tu = TexUtil::getInstance();
$currentNodeContent = $currentNode[0];
if ( $currentNodeContent instanceof Literal &&
$tu->latex_function_names( $currentNodeContent->getArg() ) ) {
$hasNamedFct = true;
}
} elseif ( $currentNode instanceof Fun1nb && $currentNode->getFname() === '\\operatorname' ) {
$hasNamedFct = true;
}
// Check if there is a valid argument as next parameter
$hasValidParameters = false;
if ( !$hasNamedFct ) {
return [ $hasNamedFct, $hasValidParameters ];
}
if ( $nextNode && !( $nextNode instanceof Literal && $nextNode->getArg() === "'" ) ) {
$hasValidParameters = true;
}
return [ $hasNamedFct, $hasValidParameters ];
}
private function squashLiterals() {
$tmp = '';
foreach ( $this->args as $arg ) {
if ( !( $arg instanceof Literal ) ) {
return;
}
// Don't squash if there is a macro in the literal
if ( preg_match( "/[\\\\]/", $arg->getArg() ) ) {
return;
}
$tmp .= $arg->getArg();
}
$this->args = [ new Literal( $tmp ) ];
$this->curly = false;
}
/** @inheritDoc */
public function renderMML( $arguments = [], &$state = [] ) {
// Everything here is for parsing displaystyle, probably refactored to WikiTexVC grammar later
$fullRenderedArray = "";
$mmlStyles = [];
$currentColor = null;
if ( array_key_exists( 'squashLiterals', $state ) ) {
$this->squashLiterals();
}
for ( $i = 0, $count = count( $this->args ); $i < $count; $i++ ) {
$current = $this->args[$i];
if ( isset( $this->args[$i + 1] ) ) {
$next = $this->args[$i + 1];
} else {
$next = null;
}
// Check for sideset
$foundSideset = $this->checkForSideset( $current, $next );
if ( $foundSideset ) {
$state["sideset"] = $foundSideset;
// Skipping the succeeding Literal
$i++;
}
// Check for limits
$foundLimits = $this->checkForLimits( $current, $next );
if ( $foundLimits[0] ) {
$state["limits"] = $foundLimits[0];
if ( $foundLimits[1] ) {
continue;
}
}
// Check for Not
$foundNot = $this->checkForNot( $current );
if ( $foundNot ) {
$state["not"] = true;
continue;
}
// Check for derivatives
$foundDeriv = $this->checkForDerivatives( $i + 1, $this->args );
if ( $foundDeriv > 0 ) {
// skip the next indices which are derivative characters
$i += $foundDeriv;
$state["deriv"] = $foundDeriv;
}
// Check if there is a new color definition and add it to state
$foundColorDef = $this->checkForColorDefinition( $current );
if ( $foundColorDef ) {
$state["colorDefinitions"][$foundColorDef["name"]] = $foundColorDef;
continue;
}
// Pass preceding color info to state
$foundColor = $this->checkForColor( $current );
if ( $foundColor[0] ) {
$currentColor = $foundColor[1];
// Skipping the color element itself for rendering
continue;
}
$styleArguments = $this->checkForStyleArgs( $current );
$foundNamedFct = $this->checkForNamedFctArgs( $current, $next );
if ( $foundNamedFct[0] ) {
$state["foundNamedFct"] = $foundNamedFct;
}
if ( $styleArguments ) {
$state["styleargs"] = $styleArguments;
$mmlStyle = new MMLmstyle( "", $styleArguments );
$fullRenderedArray .= $mmlStyle->getStart();
if ( $next instanceof TexNode && $next->isCurly() ) {
// Wrap with style-tags when the next element is a Curly which determines start and end tag.
$fullRenderedArray .= $this->createMMLwithContext( $currentColor, $next, $state, $arguments );
$fullRenderedArray .= $mmlStyle->getEnd();
$mmlStyle = null;
unset( $state["styleargs"] );
$i++;
} else {
// Start the style indicator in cases like \textstyle abc
$mmlStyles[] = $mmlStyle->getEnd();
}
} else {
$fullRenderedArray .= $this->createMMLwithContext( $currentColor, $current, $state, $arguments );
}
if ( array_key_exists( "not", $state ) ) {
unset( $state["not"] );
}
if ( array_key_exists( "limits", $state ) ) {
unset( $state["limits"] );
}
if ( array_key_exists( "deriv", $state ) ) {
unset( $state["deriv"] );
}
}
foreach ( array_reverse( $mmlStyles ) as $mmlStyleEnd ) {
$fullRenderedArray .= $mmlStyleEnd;
}
if ( $this->curly && $this->getLength() > 1 ) {
$mmlRow = new MMLmrow();
return $mmlRow->encapsulateRaw( $fullRenderedArray );
}
return $fullRenderedArray;
}
private function createMMLwithContext(
?string $currentColor, TexNode $currentNode, array &$state, array $arguments
): string {
if ( $currentColor ) {
if ( array_key_exists( "colorDefinitions", $state )
&& is_array( $state["colorDefinitions"] )
&& array_key_exists( $currentColor, $state["colorDefinitions"] ?? [] )
&& is_array( $state["colorDefinitions"][$currentColor] )
&& array_key_exists( "hex", $state["colorDefinitions"][$currentColor] )
) {
$displayedColor = $state["colorDefinitions"][$currentColor]["hex"];
} else {
$resColor = TexUtil::getInstance()->color( ucfirst( $currentColor ) );
$displayedColor = $resColor ?: $currentColor;
}
$mmlStyleColor = new MMLmstyle( "", [ "mathcolor" => $displayedColor ] );
$ret = $mmlStyleColor->encapsulateRaw( $currentNode->renderMML( $arguments, $state ) );
} else {
$ret = $currentNode->renderMML( $arguments, $state );
}
return $this->addDerivativesContext( $state, $ret );
}
/**
* If derivative was recognized, add the corresponding derivative math operator
* to the mml and wrap with msup element.
* @param array &$state state indicator which indicates derivative
* @param string $mml mathml input
* @return string mml with additional mml-elements for derivatives
*/
public function addDerivativesContext( array &$state, string $mml ): string {
if ( array_key_exists( "deriv", $state ) && $state["deriv"] > 0 ) {
$msup = new MMLmsup();
$moDeriv = new MMLmo();
if ( $state["deriv"] == 1 ) {
$derInfo = "&#x2032;";
} elseif ( $state["deriv"] == 2 ) {
$derInfo = "&#x2033;";
} elseif ( $state["deriv"] == 3 ) {
$derInfo = "&#x2034;";
} elseif ( $state["deriv"] == 4 ) {
$derInfo = "&#x2057;";
} else {
$derInfo = str_repeat( "&#x2032;", $state["deriv"] );
}
$mml = $msup->encapsulateRaw( $mml . $moDeriv->encapsulateRaw( $derInfo ) );
if ( ( $state['foundNamedFct'][0] ?? false ) && !( $state['foundNamedFct'][1] ?? true ) ) {
$mml .= MMLParsingUtil::renderApplyFunction();
}
}
return $mml;
}
/** @inheritDoc */
public function inCurlies() {
if ( isset( $this->args[0] ) && count( $this->args ) == 1 ) {
return $this->args[0]->inCurlies();
} else {
return '{' . parent::render() . '}';
}
}
/** @inheritDoc */
public function extractSubscripts() {
$y = [];
foreach ( $this->args as $x ) {
$y = array_merge( $y, $x->extractSubscripts() );
}
if ( isset( $this->args[0] ) && ( count( $this->args ) == count( $y ) ) ) {
return implode( '', $y );
}
return [];
}
/** @inheritDoc */
public function extractIdentifiers( $args = null ) {
if ( $args == null ) {
$args = $this->args;
}
$list = parent::extractIdentifiers( $args );
$outpos = 0;
$offset = 0;
$int = 0;
for ( $inpos = 0; $inpos < count( $list ); $inpos++ ) {
$outpos = $inpos - $offset;
switch ( $list[$inpos] ) {
case '\'':
$list[$outpos - 1] .= '\'';
$offset++;
break;
case '\\int':
$int++;
$offset++;
break;
case '\\mathrm{d}':
case 'd':
if ( $int ) {
$int--;
$offset++;
break;
}
// no break
default:
if ( isset( $list[0] ) ) {
$list[$outpos] = $list[$inpos];
}
}
}
return array_slice( $list, 0, count( $list ) - $offset );
}
/** @inheritDoc */
public function getModIdent() {
$y = [];
foreach ( $this->args as $x ) {
$y = array_merge( $y, $x->getModIdent() );
}
if ( isset( $this->args[0] ) && ( count( $this->args ) == count( $y ) ) ) {
return implode( "", $y );
}
return [];
}
/**
* @param TexNode|string ...$elements
*/
public function push( ...$elements ): TexArray {
self::checkInput( $elements );
array_push( $this->args, ...$elements );
return $this;
}
public function pop() {
array_splice( $this->args, 0, 1 );
}
/**
* @return TexNode|null first value
*/
public function first() {
return $this->args[0] ?? null;
}
/**
* @return TexNode|null second value
*/
public function second() {
return $this->args[1] ?? null;
}
/**
* @param TexNode|string ...$elements
*/
public function unshift( ...$elements ): TexArray {
array_unshift( $this->args, ...$elements );
return $this;
}
/**
* @throws InvalidArgumentException if args not of correct type
* @param TexNode[] $args input args
* @return void
*/
private static function checkInput( $args ): void {
foreach ( $args as $arg ) {
if ( !( $arg instanceof TexNode ) ) {
throw new InvalidArgumentException( 'Wrong input type specified in input elements.' );
}
}
}
public function render() {
if ( $this->curly ) {
return $this->inCurlies();
}
return parent::render();
}
public function isCurly(): bool {
return $this->curly;
}
public function setCurly( bool $curly = true ): TexArray {
$this->curly = $curly;
return $this;
}
/**
* @return Generator<TexNode>
*/
public function getIterator(): Generator {
yield from $this->args;
}
/**
* @return TexNode[]
*/
public function getArgs(): array {
return parent::getArgs();
}
/** @inheritDoc */
public function offsetExists( $offset ): bool {
return isset( $this->args[$offset] );
}
/** @inheritDoc */
public function offsetGet( $offset ): ?TexNode {
return $this->args[$offset] ?? null;
}
/** @inheritDoc */
public function offsetSet( $offset, $value ): void {
if ( !( $value instanceof TexNode ) ) {
throw new InvalidArgumentException( 'TexArray elements must be of type TexNode.' );
}
$this->args[$offset] = $value;
}
/** @inheritDoc */
public function offsetUnset( $offset ): void {
unset( $this->args[$offset] );
}
public function setRowSpecs( ?LengthSpec $r ) {
$this->rowSpecs = $r;
}
public function getRowSpecs(): ?LengthSpec {
return $this->rowSpecs;
}
}

File Metadata

Mime Type
text/x-php
Expires
Tue, Aug 18, 15:17 (3 w, 5 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
30/d5/baae88c22be2403db3d45234b43e
Default Alt Text
TexArray.php (15 KB)

Event Timeline