mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/Cite
synced 2024-11-23 14:36:51 +00:00
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:
parent
141b18277d
commit
60fbb32c1a
|
@ -79,6 +79,7 @@ class Cite {
|
|||
$anchorFormatter = new AnchorFormatter();
|
||||
$this->footnoteMarkFormatter = new FootnoteMarkFormatter(
|
||||
$anchorFormatter,
|
||||
new MarkSymbolRenderer( $messageLocalizer ),
|
||||
$messageLocalizer
|
||||
);
|
||||
$this->referenceListFormatter = new ReferenceListFormatter(
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
68
src/MarkSymbolRenderer.php
Normal file
68
src/MarkSymbolRenderer.php
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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(),
|
||||
|
|
|
@ -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 ) );
|
||||
}
|
||||
|
||||
}
|
||||
|
|
66
tests/phpunit/unit/MarkSymbolRendererTest.php
Normal file
66
tests/phpunit/unit/MarkSymbolRendererTest.php
Normal 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 ) );
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue