Split apart mark formatting into a simpler labeler

The new mark label renderer is a service and can be called from
Parsoid.

Bug: T377454
Change-Id: I6f4983c4288bf29954ad4e5fa3309f9bdf48215d
This commit is contained in:
Adam Wight 2024-10-23 18:39:59 +02:00 committed by Awight
parent 141b18277d
commit 60fbb32c1a
6 changed files with 151 additions and 99 deletions

View file

@ -79,6 +79,7 @@ class Cite {
$anchorFormatter = new AnchorFormatter();
$this->footnoteMarkFormatter = new FootnoteMarkFormatter(
$anchorFormatter,
new MarkSymbolRenderer( $messageLocalizer ),
$messageLocalizer
);
$this->referenceListFormatter = new ReferenceListFormatter(

View file

@ -13,17 +13,17 @@ use MediaWiki\Parser\Sanitizer;
*/
class FootnoteMarkFormatter {
/** @var array<string,string[]> In-memory cache for the cite_link_label_group-… link label lists */
private array $linkLabels = [];
private AnchorFormatter $anchorFormatter;
private MarkSymbolRenderer $markSymbolRenderer;
private ReferenceMessageLocalizer $messageLocalizer;
public function __construct(
AnchorFormatter $anchorFormatter,
MarkSymbolRenderer $markSymbolRenderer,
ReferenceMessageLocalizer $messageLocalizer
) {
$this->anchorFormatter = $anchorFormatter;
$this->markSymbolRenderer = $markSymbolRenderer;
$this->messageLocalizer = $messageLocalizer;
}
@ -38,7 +38,7 @@ class FootnoteMarkFormatter {
* @return string HTML
*/
public function linkRef( Parser $parser, ReferenceStackItem $ref ): string {
$label = $this->makeLabel( $ref->group, $ref->number, $ref->extendsIndex );
$label = $this->markSymbolRenderer->makeLabel( $ref->group, $ref->number, $ref->extendsIndex );
$key = $ref->name ?? $ref->key;
// TODO: Use count without decrementing.
@ -54,51 +54,4 @@ class FootnoteMarkFormatter {
)->plain()
);
}
public function makeLabel( string $group, int $number, ?int $extendsIndex = null ): string {
$label = $this->fetchCustomizedLinkLabel( $group, $number ) ??
$this->makeDefaultLabel( $group, $number );
if ( $extendsIndex !== null ) {
// TODO: design better behavior, especially when using custom group markers.
$label .= '.' . $this->messageLocalizer->localizeDigits( (string)$extendsIndex );
}
return $label;
}
public function makeDefaultLabel( string $group, int $number ): string {
$label = $this->messageLocalizer->localizeDigits( (string)$number );
return $group === Cite::DEFAULT_GROUP ? $label : "$group $label";
}
/**
* Generate a custom format link for a group given an offset, e.g.
* the second <ref group="foo"> is b if $this->mLinkLabels["foo"] =
* [ 'a', 'b', 'c', ...].
* Return an error if the offset > the # of array items
*
* @param string $group
* @param int $number
*
* @return ?string Returns null if no custom label can be found
*/
private function fetchCustomizedLinkLabel( string $group, int $number ): ?string {
if ( $group === Cite::DEFAULT_GROUP ) {
return null;
}
$message = "cite_link_label_group-$group";
if ( !array_key_exists( $group, $this->linkLabels ) ) {
$msg = $this->messageLocalizer->msg( $message );
$this->linkLabels[$group] = $msg->isDisabled() ? [] : preg_split( '/\s+/', $msg->plain() );
}
// Expected behavior for groups without custom labels
if ( !$this->linkLabels[$group] ) {
return null;
}
// Error message in case we run out of custom labels
return $this->linkLabels[$group][$number - 1] ?? null;
}
}

View file

@ -0,0 +1,68 @@
<?php
namespace Cite;
/**
* Render the label for footnote marks, for example "1", "2",
*
* Marks can be customized by group.
*
* @license GPL-2.0-or-later
*/
class MarkSymbolRenderer {
/** @var array<string,string[]> In-memory cache for the cite_link_label_group-… link label lists */
private array $legacyLinkLabels = [];
private ReferenceMessageLocalizer $messageLocalizer;
public function __construct(
ReferenceMessageLocalizer $messageLocalizer
) {
// TODO: deprecate the i18n mechanism.
$this->messageLocalizer = $messageLocalizer;
}
public function makeLabel( string $group, int $number, ?int $extendsIndex = null ): string {
$label = $this->fetchLegacyCustomLinkLabel( $group, $number ) ??
$this->makeDefaultLabel( $group, $number );
if ( $extendsIndex !== null ) {
// TODO: design better behavior, especially when using custom group markers.
$label .= '.' . $this->messageLocalizer->localizeDigits( (string)$extendsIndex );
}
return $label;
}
private function makeDefaultLabel( string $group, int $number ): string {
$label = $this->messageLocalizer->localizeDigits( (string)$number );
return $group === Cite::DEFAULT_GROUP ? $label : "$group $label";
}
/**
* Look up the symbol in a literal sequence stored in a local system message override.
*
* @deprecated since 1.44
*/
private function fetchLegacyCustomLinkLabel( string $group, int $number ): ?string {
if ( $group === Cite::DEFAULT_GROUP ) {
// TODO: Possibly make the default group configurable, eg. to use a
// different numeral system than the content language or Western
// Arabic.
return null;
}
// TODO: deprecate this mechanism.
$message = "cite_link_label_group-$group";
if ( !array_key_exists( $group, $this->legacyLinkLabels ) ) {
$msg = $this->messageLocalizer->msg( $message );
$this->legacyLinkLabels[$group] = $msg->isDisabled() ? [] : preg_split( '/\s+/', $msg->plain() );
}
// Expected behavior for groups without custom labels
if ( !$this->legacyLinkLabels[$group] ) {
return null;
}
return $this->legacyLinkLabels[$group][$number - 1] ?? null;
}
}

View file

@ -1,5 +1,7 @@
<?php
use Cite\MarkSymbolRenderer;
use Cite\ReferenceMessageLocalizer;
use Cite\ReferencePreviews\ReferencePreviewsContext;
use Cite\ReferencePreviews\ReferencePreviewsGadgetsIntegration;
use MediaWiki\MediaWikiServices;
@ -17,6 +19,13 @@ return [
null
);
},
'Cite.MarkSymbolRenderer' => static function ( MediaWikiServices $services ): MarkSymbolRenderer {
return new MarkSymbolRenderer(
new ReferenceMessageLocalizer(
$services->getContentLanguage()
)
);
},
'Cite.ReferencePreviewsContext' => static function ( MediaWikiServices $services ): ReferencePreviewsContext {
return new ReferencePreviewsContext(
$services->getMainConfig(),

View file

@ -3,13 +3,12 @@
namespace Cite\Tests\Integration;
use Cite\AnchorFormatter;
use Cite\Cite;
use Cite\FootnoteMarkFormatter;
use Cite\MarkSymbolRenderer;
use Cite\ReferenceMessageLocalizer;
use Cite\Tests\TestUtils;
use MediaWiki\Message\Message;
use MediaWiki\Parser\Parser;
use Wikimedia\TestingAccessWrapper;
/**
* @covers \Cite\FootnoteMarkFormatter
@ -41,10 +40,12 @@ class FootnoteMarkFormatterTest extends \MediaWikiIntegrationTestCase {
return $msg;
}
);
$markSymbolRenderer = new MarkSymbolRenderer( $messageLocalizer );
$mockParser = $this->createNoOpMock( Parser::class, [ 'recursiveTagParse' ] );
$mockParser->method( 'recursiveTagParse' )->willReturnArgument( 0 );
$formatter = new FootnoteMarkFormatter(
$anchorFormatter,
$markSymbolRenderer,
$messageLocalizer
);
@ -124,50 +125,4 @@ class FootnoteMarkFormatterTest extends \MediaWikiIntegrationTestCase {
],
];
}
/**
* @dataProvider provideCustomizedLinkLabels
*/
public function testFetchCustomizedLinkLabel( $expectedLabel, $offset, $group, $labelList ) {
$mockMessageLocalizer = $this->createMock( ReferenceMessageLocalizer::class );
$mockMessageLocalizer->method( 'msg' )->willReturnCallback(
function ( ...$args ) use ( $labelList ) {
$msg = $this->createMock( Message::class );
$msg->method( 'isDisabled' )->willReturn( $labelList === null );
$msg->method( 'plain' )->willReturn( $labelList );
return $msg;
}
);
/** @var FootnoteMarkFormatter $formatter */
$formatter = TestingAccessWrapper::newFromObject( new FootnoteMarkFormatter(
$this->createMock( AnchorFormatter::class ),
$mockMessageLocalizer
) );
$output = $formatter->fetchCustomizedLinkLabel( $group, $offset );
$this->assertSame( $expectedLabel, $output );
}
public static function provideCustomizedLinkLabels() {
yield [ null, 1, '', null ];
yield [ null, 2, '', null ];
yield [ null, 1, 'foo', null ];
yield [ null, 2, 'foo', null ];
yield [ 'a', 1, 'foo', 'a b c' ];
yield [ 'b', 2, 'foo', 'a b c' ];
yield [ 'å', 1, 'foo', 'å β' ];
yield [ null, 4, 'foo', 'a b c' ];
}
public function testDefaultGroupCannotHaveCustomLinkLabels() {
/** @var FootnoteMarkFormatter $formatter */
$formatter = TestingAccessWrapper::newFromObject( new FootnoteMarkFormatter(
$this->createNoOpMock( AnchorFormatter::class ),
// Assert that ReferenceMessageLocalizer::msg( 'cite_link_label_group-' ) isn't called
$this->createNoOpMock( ReferenceMessageLocalizer::class )
) );
$this->assertNull( $formatter->fetchCustomizedLinkLabel( Cite::DEFAULT_GROUP, 1 ) );
}
}

View file

@ -0,0 +1,66 @@
<?php
namespace Cite\Tests\Unit;
use Cite\Cite;
use Cite\MarkSymbolRenderer;
use Cite\ReferenceMessageLocalizer;
use MediaWiki\Message\Message;
/**
* @covers \Cite\MarkSymbolRenderer
* @license GPL-2.0-or-later
*/
class MarkSymbolRendererTest extends \MediaWikiUnitTestCase {
/**
* @dataProvider provideCustomizedLinkLabels
*/
public function testMakeLabel( ?string $expectedLabel, int $offset, string $group, ?string $labelList ) {
$mockMessageLocalizer = $this->createMock( ReferenceMessageLocalizer::class );
$mockMessageLocalizer->method( 'msg' )->willReturnCallback(
function ( ...$args ) use ( $labelList ) {
$msg = $this->createMock( Message::class );
$msg->method( 'isDisabled' )->willReturn( $labelList === null );
$msg->method( 'plain' )->willReturn( $labelList );
return $msg;
}
);
$mockMessageLocalizer->method( 'localizeDigits' )->willReturnCallback(
static function ( $number ) {
return (string)$number;
}
);
$renderer = new MarkSymbolRenderer( $mockMessageLocalizer );
$output = $renderer->makeLabel( $group, $offset );
$this->assertSame( $expectedLabel, $output );
}
public static function provideCustomizedLinkLabels() {
yield [ '1', 1, '', null ];
yield [ '2', 2, '', null ];
yield [ 'foo 1', 1, 'foo', null ];
yield [ 'foo 2', 2, 'foo', null ];
yield [ 'a', 1, 'foo', 'a b c' ];
yield [ 'b', 2, 'foo', 'a b c' ];
yield [ 'å', 1, 'foo', 'å β' ];
yield [ 'foo 4', 4, 'foo', 'a b c' ];
}
public function testDefaultGroupCannotHaveCustomLinkLabels() {
$mockMessageLocalizer = $this->createMock( ReferenceMessageLocalizer::class );
$mockMessageLocalizer->method( 'localizeDigits' )->willReturnCallback(
static function ( $number ) {
return (string)$number;
}
);
// Assert that ReferenceMessageLocalizer::msg( 'cite_link_label_group-' )
// isn't called by not defining the ->msg method.
$renderer = new MarkSymbolRenderer( $mockMessageLocalizer );
$this->assertSame( '1', $renderer->makeLabel( Cite::DEFAULT_GROUP, 1 ) );
}
}