Page MenuHomeWickedGov Phorge

SectionMetadata.php
No OneTemporary

Size
14 KB
Referenced Files
None
Subscribers
None

SectionMetadata.php

<?php
declare( strict_types = 1 );
namespace Wikimedia\Parsoid\Core;
use Wikimedia\JsonCodec\JsonCodecable;
use Wikimedia\JsonCodec\JsonCodecableTrait;
use Wikimedia\Parsoid\Utils\CompatJsonCodec;
/**
* Section metadata for generating TOC.
*
* This is not the complete data for the article section, just the
* information needed to generate the table of contents.
*
* For now, this schema matches whatever is generated by Parser.php.
* Parsoid will attempt to match this output for now.
*
* Parser.php::finalizeHeadings() is the authoritative source for how
* some of these properties are computed right now, especially for the
* $line, $anchor, and $linkAnchor properties below.
*
* Linker.php::tocLine() and ::makeHeadline() demonstrate how these
* properties are used to create headings and table of contents lines.
*/
class SectionMetadata implements \JsonSerializable, JsonCodecable {
use JsonCodecableTrait;
/**
* The heading tag level: a 1 here means an <H1> tag was used, a
* 2 means an <H2> tag was used, etc.
*/
public int $hLevel;
/**
* This is a one-indexed TOC level and the nesting level.
* So, if a page has a H2-H4-H6, then, those levels 2,4,6
* correspond to TOC-levels 1,2,3.
*/
public int $tocLevel;
/**
* HTML heading of the section. Only a narrow set of HTML tags are allowed here.
*
* This starts with the parsed headline seen in wikitext and
* - replaces links with link text
* - processes extension strip markers
* - removes style, script tags
* - strips all HTML tags except the following tags (from Parser.php)
* . <sup> and <sub> (T10393)
* . <i> (T28375)
* . <b> (r105284)
* . <bdi> (T74884)
* . <span dir="rtl"> and <span dir="ltr"> (T37167)
* . <s> and <strike> (T35715)
* . <q> (T251672)
* We strip any parameter from accepted tags, except dir="rtl|ltr" from <span>,
* to allow setting directionality in toc items.
*
* @note This should be converted into the proper html variant.
*/
public string $line;
/**
* TOC number string (3.1.3, 4.5.2, etc.)
*
* @note This should be localized into the parser target language.
*/
public string $number;
/**
* Section id (integer, assigned in depth first traversal order)
* Template generated sections get a "T-" prefix.
*/
public string $index;
/**
* The title of the page that generated this heading.
* For template-generated sections, this will be the template title.
* This string is in "prefixed DB key" format.
*/
public ?string $fromTitle;
/**
* Codepoint offset where the section shows up in wikitext; this is null
* if this section comes from a template, if it comes from a literal
* HTML <h_> tag, or otherwise doesn't correspond to a "preprocessor
* section".
* @note This is measured in codepoints, not bytes; you should use
* appropriate multi-byte aware string functions, *not* substr().
* Similarly, in JavaScript, be careful not to confuse JavaScript
* UCS-2 "characters" with codepoints.
*/
public ?int $codepointOffset;
/**
* Anchor attribute.
*
* This property is the "true" value of the ID attribute, and should be
* used when looking up a heading or setting an attribute, for example
* using Document.getElementById() or Element.setAttribute('id',...).
*
* This value is *not* HTML-entity escaped; if you are writing HTML
* as a literal string, you should still entity-escape ampersands and
* single/double quotes as appropriate.
*
* This value is *not* URL-escaped either; instead use the `linkAnchor`
* property if you are constructing a URL to target this section.
*
* The anchor attribute is based on the $line property, but does extra
* processing to turn it into a valid attribute:
* - strip all HTML tags,
* - normalizes section name
* - normalizes section name whitespace
* - decodes char references
* - makes it a valid HTML id attribute value
* (HTML5 / HTML4 based on $wgFragmentMode property)
* - dedupes (case-insensitively) identical anchors by adding "_$n" suffixes
*/
public string $anchor;
/**
* Anchor URL fragment.
*
* This is very similar to the $anchor property, but is appropriately
* URL-escaped to make it appropriate to use in constructing a URL
* fragment link. You should almost always prepend a `#` symbol
* to `linkAnchor` if you are using it correctly. You are still
* responsible for HTML-escaping the resulting URL if you are emitting
* this as an HTML attribute.
*/
public string $linkAnchor;
/**
* Arbitrary data attached to this section by extensions. This
* data will be stored and cached in the ParserOutput object along
* with the rest of the section data, and made available to external
* clients via the action API.
*
* This method is provided to overcome the unsafe practice of attaching
* extra information to a section by directly assigning member variables.
*
* See ParserOutput::setExtensionData() for more information on typical
* use.
*/
private array $extensionData;
/**
* @param int $tocLevel One-indexed TOC level and the nesting level
* @param int $hLevel The heading tag level
* @param string $line Stripped headline text
* @param string $number TOC number string (3.1.3, 4.5.2, etc)
* @param string $index Section id
* @param ?string $fromTitle The title of the page or template that
* generated this heading, or null.
* @param ?int $codepointOffset Codepoint offset (# of characters) where the
* section shows up in wikitext, or null if this doesn't correspond to
* a "preprocesor section". (Be careful if using JavaScript, as
* JavaScript "characters" are UCS-2 encoded and don't correspond
* directly to code points.)
* @param string $anchor "True" value of the ID attribute
* @param string $linkAnchor URL-escaped value of the anchor, for use in
* constructing a URL fragment link
* @param ?array $extensionData Extension data passed in as an associative array
*/
public function __construct(
// This is a great candidate for named arguments in PHP 8.0+
int $tocLevel = 0,
int $hLevel = -1,
string $line = '',
string $number = '',
string $index = '',
?string $fromTitle = null,
?int $codepointOffset = null,
string $anchor = '',
string $linkAnchor = '',
?array $extensionData = null
) {
$this->tocLevel = $tocLevel;
$this->line = $line;
$this->hLevel = $hLevel;
$this->number = $number;
$this->index = $index;
$this->fromTitle = $fromTitle;
$this->codepointOffset = $codepointOffset;
$this->anchor = $anchor;
$this->linkAnchor = $linkAnchor;
$this->extensionData = $extensionData ?? [];
}
/**
* Attaches arbitrary data to this SectionMetadata object. This
* can be used to store some information about this section in the
* ParserOutput object for later use during page output. The data
* will be cached along with the ParserOutput object.
*
* This method is provided to overcome the unsafe practice of
* attaching extra information to a section by directly assigning
* member variables.
*
* See ParserOutput::setExtensionData() in core for further information
* about typical usage in hooks.
*
* Setting conflicting values for the same key is not allowed.
* If you call ::setExtensionData() multiple times with the same key
* on a SectionMetadata, 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 more complex values than scalar values are supported,
* TOCData::__clone should be updated to take that into account.
*
* @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 SectionMetadata. This can be used
* to store some information about the section 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 SectionMetadata.
*
* @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;
}
/**
* Alias for :toLegacy(), for b/c compatibility only.
* @deprecated
* @return array
*/
public function toArray(): array {
return $this->toLegacy();
}
/**
* Alias for :fromLegacy(), for b/c compatibility only.
* @deprecated
* @param array $data
* @return SectionMetadata
*/
public static function fromArray( array $data ): SectionMetadata {
return self::fromLegacy( $data );
}
/**
* Create a new SectionMetadata object from an array in the legacy
* format returned by the action API.
*
* This is useful for backward-compatibility, but is expected to
* be replaced by conversion to/from JSON in the future.
*
* @param array $data Associative array with section metadata
* @return SectionMetadata
*/
public static function fromLegacy( array $data ): SectionMetadata {
return new SectionMetadata(
$data['toclevel'] ?? 0,
(int)( $data['level'] ?? -1 ),
$data['line'] ?? '',
$data['number'] ?? '',
$data['index'] ?? '',
( $data['fromtitle'] ?? false ) ?: null,
$data['byteoffset'] ?? null, // T319141: actually "codepoint offset"
$data['anchor'] ?? '',
$data['linkAnchor'] ?? $data['anchor'] ?? '',
$data['extensionData'] ?? null
);
}
/**
* Return as associative array, in the format returned by the
* action API (including the order of fields and the value types).
*
* This is helpful as b/c support while we transition to objects.
* @return array
*/
public function toLegacy(): array {
$ret = [
'toclevel' => $this->tocLevel,
// cast $level to string in order to keep b/c for the parse api
'level' => (string)$this->hLevel,
'line' => $this->line,
'number' => $this->number,
'index' => $this->index,
'fromtitle' => $this->fromTitle ?? false,
// T319141: legacy 'byteoffset' is actually "codepoint offset"
'byteoffset' => $this->codepointOffset,
'anchor' => $this->anchor,
'linkAnchor' => $this->linkAnchor,
];
// Micro-opt: Output 'extensionData' conditionally to avoid bloat
if ( $this->extensionData ) {
$ret['extensionData'] = $this->extensionData;
}
return $ret;
}
/**
* @inheritDoc
*/
public function jsonSerialize(): array {
return $this->toLegacy();
}
// JsonCodecable interface
/** @inheritDoc */
public function toJsonArray(): array {
$ret = [];
if ( $this->tocLevel !== 0 ) {
$ret['tocLevel'] = $this->tocLevel;
}
if ( $this->hLevel !== -1 ) {
$ret['hLevel'] = $this->hLevel;
}
if ( $this->line !== '' ) {
$ret['line'] = $this->line;
}
if ( $this->number !== '' ) {
$ret['number'] = $this->number;
}
if ( $this->index !== '' ) {
$ret['index'] = $this->index;
}
if ( $this->fromTitle !== null ) {
$ret['fromTitle'] = $this->fromTitle;
}
if ( $this->codepointOffset !== null ) {
$ret['codepointOffset'] = $this->codepointOffset;
}
if ( $this->anchor !== '' ) {
$ret['anchor'] = $this->anchor;
}
if ( $this->linkAnchor !== $this->anchor ) {
$ret['linkAnchor'] = $this->linkAnchor;
}
if ( $this->extensionData ) {
$ret['extensionData'] = $this->extensionData;
}
return $ret;
}
/** @inheritDoc */
public static function newFromJsonArray( array $json ) {
return new SectionMetadata(
$json['tocLevel'] ?? 0,
$json['hLevel'] ?? -1,
$json['line'] ?? '',
$json['number'] ?? '',
$json['index'] ?? '',
$json['fromTitle'] ?? null,
$json['codepointOffset'] ?? null,
$json['anchor'] ?? '',
$json['linkAnchor'] ?? $json['anchor'] ?? '',
$json['extensionData'] ?? null
);
}
// Pretty-printing
/**
* For use in parser tests and wherever else humans might appreciate
* some formatting in the JSON encoded output. For now, nothing special.
* @param int $indent Additional indentation to apply (defaults to zero)
* @return string
*/
public function prettyPrint( int $indent = 0 ): string {
# Basic info
$buf = str_repeat( ' ', $indent + $this->tocLevel ) . "h{$this->hLevel}";
$buf .= " index:{$this->index} toclevel:$this->tocLevel number:{$this->number}";
# Optional information
$title = $this->fromTitle ?? "NULL";
$offset = $this->codepointOffset ?? "NULL";
$buf .= " title:{$title} off:{$offset}";
# Anchors & link text
if ( $this->anchor === $this->linkAnchor ) {
$buf .= " anchor/linkAnchor:{$this->anchor}";
} else {
$buf .= " anchor:{$this->anchor} linkAnchor:{$this->linkAnchor}";
}
$line = $this->line;
if ( str_contains( $line, "\n" ) ) {
// Handle cases where $line has "funny" characters
$line = json_encode( $line );
}
$buf .= " line:{$line}";
# Extension data
if ( $this->extensionData ) {
$codec = new CompatJsonCodec();
$buf .= " ext:" . json_encode( $codec->toJsonArray( $this->extensionData ) );
}
return $buf;
}
}

File Metadata

Mime Type
text/x-php
Expires
Wed, Aug 19, 03:28 (1 w, 5 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
9f/34/1dd749fe8147521c5329f37725a3
Default Alt Text
SectionMetadata.php (14 KB)

Event Timeline