Page MenuHomeWickedGov Phorge

TOCData.php
No OneTemporary

Size
10 KB
Referenced Files
None
Subscribers
None

TOCData.php

<?php
declare( strict_types = 1 );
namespace Wikimedia\Parsoid\Core;
use Wikimedia\JsonCodec\Hint;
use Wikimedia\JsonCodec\JsonCodecable;
use Wikimedia\JsonCodec\JsonCodecableTrait;
use Wikimedia\Parsoid\Utils\CompatJsonCodec;
/**
* Table of Contents data, including an array of section metadata.
*
* This is simply an array of SectionMetadata objects for now along
* with extension data, but may include additional ToC properties in
* the future.
*/
class TOCData implements \JsonSerializable, JsonCodecable {
use JsonCodecableTrait;
/**
* The sections in this Table of Contents.
* @var SectionMetadata[]
*/
private array $sections;
/**
* Arbitrary data attached to this Table of Contents by
* extensions. This data will be stored and cached in the
* ParserOutput object along with the rest of the table of
* contents data, and made available to external clients via the
* action API.
*
* See ParserOutput::setExtensionData() for more information on typical
* use, and SectionMetadata::setExtensionData() for a method appropriate
* for attaching information to a specific section of the ToC.
*/
private array $extensionData = [];
/**
* --------------------------------------------------
* These next 4 properties are temporary state needed
* to construct section metadata objects used in TOC.
* These state properties are not useful once that is
* done and will not be exported or serialized.
* --------------------------------------------------
*/
/** @var int Temporary TOC State */
private $tocLevel = 0;
/** @var int Temporary TOC State */
private $prevLevel = 0;
/** @var array<int> Temporary TOC State */
private $levelCount = [];
/** @var array<int> Temporary TOC State */
private $subLevelCount = [];
/**
* Create a new TOCData object with the given sections and no
* extension data.
* @param SectionMetadata ...$sections
*/
public function __construct( ...$sections ) {
$this->sections = $sections;
}
/**
* Return current TOC level while headings are being
* processed and section metadata is being constructed.
* @return int
*/
public function getCurrentTOCLevel(): int {
return $this->tocLevel;
}
/**
* Add a new section to this TOCData.
* @param SectionMetadata $s
*/
public function addSection( SectionMetadata $s ) {
$this->sections[] = $s;
}
/**
* Get the list of sections in the TOCData.
* @return SectionMetadata[]
*/
public function getSections() {
return $this->sections;
}
/**
* Attaches arbitrary data to this TOCData object. This can be
* used to store some information about the table of contents in
* the ParserOutput object for later use during page output. The
* data will be cached along with the ParserOutput object.
*
* See ParserOutput::setExtensionData() in core for further information
* about typical usage in hooks, and SectionMetadata::setExtensionData()
* for a similar method appropriate for information about a specific
* section of the ToC.
*
* Setting conflicting values for the same key is not allowed.
* If you call ::setExtensionData() multiple times with the same key
* on a TOCData, is is expected that the value will be identical
* each time. If you want to collect multiple pieces of data under a
* single key, use ::appendExtensionData().
*
* @note Only scalar values (numbers, strings, or arrays) are
* supported as a value. (A future revision will allow anything
* that core's JsonCodec can handle.) Attempts to set other types
* as extension data values will break ParserCache for the page.
*
* @todo When values more complex than scalar values get supported,
* __clone needs to be updated accordingly.
*
* @param string $key The key for accessing the data. Extensions
* should take care to avoid conflicts in naming keys. It is
* suggested to use the extension's name as a prefix. Using
* the prefix `mw:` is reserved for core.
*
* @param mixed $value The value to set.
* Setting a value to null is equivalent to removing the value.
*/
public function setExtensionData( string $key, $value ): void {
if (
array_key_exists( $key, $this->extensionData ) &&
$this->extensionData[$key] !== $value
) {
throw new \InvalidArgumentException( "Conflicting data for $key" );
}
if ( $value === null ) {
unset( $this->extensionData[$key] );
} else {
$this->extensionData[$key] = $value;
}
}
/**
* Appends arbitrary data to this TOCData. This can be used to
* store some information about the table of contents in the
* ParserOutput object for later use during page output.
*
* See ::setExtensionData() for more details on rationale and use.
*
* @param string $key The key for accessing the data. Extensions should take care to avoid
* conflicts in naming keys. It is suggested to use the extension's name as a prefix.
*
* @param int|string $value The value to append to the list.
* @return never This method is not yet implemented.
*/
public function appendExtensionData( string $key, $value ): void {
// This implementation would mirror that of
// ParserOutput::appendExtensionData, but let's defer implementing
// this until we're sure we need it. In particular, we might need
// to figure out how a merge on section data is expected to work
// before we can determine the right semantics for this.
throw new \InvalidArgumentException( "Not yet implemented" );
}
/**
* Gets extension data previously attached to this TOCData.
*
* @param string $key The key to look up
* @return mixed|null The value(s) previously set for the given key using
* ::setExtensionData() or ::appendExtensionData(), or null if no
* value was set for this key.
*/
public function getExtensionData( $key ) {
$value = $this->extensionData[$key] ?? null;
return $value;
}
/**
* Return as associative array, in the legacy format returned by the
* action API.
*
* This is helpful as b/c support while we transition to objects,
* but it drops some properties from this class and shouldn't be used
* in new code.
* @return array
*/
public function toLegacy(): array {
return array_map(
static function ( $s ) {
return $s->toLegacy();
},
$this->sections
);
}
/**
* Create a new TOCData object from the legacy associative array format.
*
* This is used for backward compatibility, but the associative array
* format does not include any properties of the TOCData other than the
* section list.
*
* @param array $data Associative array with ToC data in legacy format
* @return TOCData
*/
public static function fromLegacy( array $data ): TOCData {
// The legacy format has no way to represent extension data.
$sections = array_map(
static function ( $d ) {
return SectionMetadata::fromLegacy( $d );
},
$data
);
return new TOCData( ...$sections );
}
/**
* @param int $oldLevel level of the heading (H1/H2, etc.)
* @param int $level level of the heading (H1/H2, etc.)
* @param SectionMetadata $metadata This metadata will be updated
* This logic is copied from Parser.php::finalizeHeadings
*/
public function processHeading( int $oldLevel, int $level, SectionMetadata $metadata ): void {
if ( $this->tocLevel ) {
$this->prevLevel = $oldLevel;
}
if ( $level > $this->prevLevel ) {
# increase TOC level
$this->tocLevel++;
$this->subLevelCount[$this->tocLevel] = 0;
} elseif ( $level < $this->prevLevel && $this->tocLevel > 1 ) {
# Decrease TOC level, find level to jump to
for ( $i = $this->tocLevel; $i > 0; $i-- ) {
if ( $this->levelCount[$i] === $level ) {
# Found last matching level
$this->tocLevel = $i;
break;
} elseif ( $this->levelCount[$i] < $level ) {
# Found first matching level below current level
$this->tocLevel = $i + 1;
break;
}
}
if ( $i === 0 ) {
$this->tocLevel = 1;
}
}
$this->levelCount[$this->tocLevel] = $level;
# count number of headlines for each level
$this->subLevelCount[$this->tocLevel]++;
$numbering = '';
$dot = false;
for ( $i = 1; $i <= $this->tocLevel; $i++ ) {
if ( !empty( $this->subLevelCount[$i] ) ) {
if ( $dot ) {
$numbering .= '.';
}
$numbering .= $this->subLevelCount[$i];
$dot = true;
}
}
$metadata->hLevel = $level;
$metadata->tocLevel = $this->tocLevel;
$metadata->number = $numbering;
}
/**
* Serialize all data in the TOCData as JSON.
*
* Unlike the `:toLegacy()` method, this method will include *all*
* of the properties in the TOCData so that the serialization is
* reversible.
*
* @inheritDoc
*/
public function jsonSerialize(): array {
# T312589 explicitly calling jsonSerialize() on the elements of
# $this->sections will be unnecessary in the future.
$sections = array_map(
static function ( SectionMetadata $s ) {
return $s->jsonSerialize();
},
$this->sections
);
return [
'sections' => $sections,
'extensionData' => $this->extensionData,
];
}
// JsonCodecable interface
/** @inheritDoc */
public function toJsonArray(): array {
return [
'sections' => $this->sections,
'extensionData' => $this->extensionData,
];
}
/** @inheritDoc */
public static function newFromJsonArray( array $json ) {
$tocData = new TOCData( ...$json['sections'] );
foreach ( $json['extensionData'] as $key => $value ) {
$tocData->setExtensionData( $key, $value );
}
return $tocData;
}
/** @inheritDoc */
public static function jsonClassHintFor( string $keyName ) {
if ( $keyName === 'sections' ) {
return Hint::build( SectionMetadata::class, Hint::LIST );
}
return null;
}
// Pretty-printing
/**
* For use in parser tests and wherever else humans might appreciate
* some formatting in the JSON encoded output.
* @return string
*/
public function prettyPrint(): string {
$out = [ "Sections:" ];
foreach ( $this->sections as $s ) {
$out[] = $s->prettyPrint();
}
if ( $this->extensionData ) {
$out[] = "Extension Data:";
$codec = new CompatJsonCodec();
$out[] = json_encode( $codec->toJsonArray( $this->extensionData ) );
}
return implode( "\n", $out );
}
public function __clone() {
foreach ( $this->sections as $k => $v ) {
$this->sections[$k] = clone $v;
}
}
}

File Metadata

Mime Type
text/x-php
Expires
Sat, Oct 3, 05:51 (2 w, 4 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
83/e6/bac8be15f95fccd650f906d12474
Default Alt Text
TOCData.php (10 KB)

Event Timeline