Implement datatype 'Math' for Wikidata

This change implements all components to use the datatype 'Math' in Wikidata.
Because 'String 'is used as value type, only the Formatter and Validator are needed to be implemended.

The components are:
* hooks
* Formatter class
* Validator class
* Test cases

Bug: T67397
Change-Id: Ic64fd6c8560f48052e2db24ae1f013d48a82b5e9
This commit is contained in:
lyrianos93 2015-12-14 20:26:29 +00:00
parent 2938d69e0b
commit 946a18d14c
6 changed files with 368 additions and 1 deletions

83
MathFormatter.php Normal file
View file

@ -0,0 +1,83 @@
<?php
use DataValues\StringValue;
use ValueFormatters\Exceptions\MismatchingDataValueTypeException;
use ValueFormatters\FormattingException;
use ValueFormatters\ValueFormatter;
use Wikibase\Lib\SnakFormatter;
/*
* Formats the tex string based on the known formats
* * text/plain: used in the value input field of Wikidata
* * text/x-wiki: wikitext
* * text/html: used in Wikidata to display the value of properties
* Formats can look like this: "text/html; disposition=widget"
* or just "text/plain"
*/
class MathFormatter implements ValueFormatter {
private $format;
/*
* Loads format to distinguish the type of formatting
*
* @param string $format
*
* @throws InvalidArgumentException
*/
public function __construct( $format ) {
switch ( $format ) {
case ( SnakFormatter::FORMAT_HTML ):
case ( SnakFormatter::FORMAT_HTML_DIFF ):
case ( SnakFormatter::FORMAT_HTML_WIDGET ):
case ( SnakFormatter::FORMAT_WIKI ):
case ( SnakFormatter::FORMAT_PLAIN ):
$this->format = $format;
break;
default:
throw new InvalidArgumentException( 'Unsupported output format: ' . $format );
}
}
/*
*
* @param StringValue $value
*
* @return string
* @throws \ValueFormatters\Exceptions\MismatchingDataValueTypeException
*/
public function format( $value ) {
if ( !( $value instanceof StringValue ) ) {
throw new MismatchingDataValueTypeException( 'StringValue', get_class( $value ) );
}
$tex = $value->getValue();
switch ( $this->format ) {
case ( SnakFormatter::FORMAT_PLAIN ):
return "$tex";
case ( SnakFormatter::FORMAT_WIKI ):
return "<math>$tex</math>";
case ( SnakFormatter::FORMAT_HTML ):
case ( SnakFormatter::FORMAT_HTML_WIDGET ):
case ( SnakFormatter::FORMAT_HTML_DIFF ):
$renderer = new MathMathML( $tex );
if ( $renderer->checkTex() ) {
if ( $renderer->render() ) {
return $renderer->getHtmlOutput();
}
}
// TeX string is not valid or rendering failed
return $renderer->getLastError();
}
}
/**
*
* @return format
*/
public function getFormat() {
return $this->format;
}
}

50
MathValidator.php Normal file
View file

@ -0,0 +1,50 @@
<?php
use ValueFormatters\Exceptions\MismatchingDataValueTypeException;
use ValueValidators\Result;
use ValueValidators\ValueValidator;
use ValueValidators\Error;
use DataValues\StringValue;
// @author Duc Linh Tran, Julian Hilbig, Moritz Schubotz
class MathValidator implements ValueValidator {
/**
* Validates a value with MathInputCheckRestbase
*
* @param mixed $value The value to validate
*
* @return \ValueValidators\Result
* @throws ValueFormatters\Exceptions\MismatchingDataValueTypeException
*/
public function validate( $value ) {
if ( !( $value instanceof StringValue ) ) {
throw new MismatchingDataValueTypeException( 'StringValue', get_class( $value ) );
}
// get input String from value
$tex = $value->getValue();
$checker = new MathInputCheckRestbase( $tex );
if ( $checker->isValid() ) {
return Result::newSuccess();
}
// TeX string is not valid
return Result::newError(
array(
Error::newError( null, null, 'malformed-value', $checker->getError() )
)
);
}
/**
* @see ValueValidator::setOptions()
*
* @param array $options
*/
public function setOptions( array $options ) {
// Do nothing. This method shouldn't even be in the interface.
}
}

56
MathWikidataHook.php Normal file
View file

@ -0,0 +1,56 @@
<?php
use ValueFormatters\FormatterOptions;
use ValueParsers\StringParser;
use Wikibase\Repo\Parsers\WikibaseStringValueNormalizer;
use Wikibase\Repo\Validators\CompositeValidator;
use Wikibase\Repo\WikibaseRepo;
class MathWikidataHook {
/*
* Add Datatype "Math" to the Wikibase Repository
*/
public static function onWikibaseRepoDataTypes( array &$dataTypeDefinitions ) {
$dataTypeDefinitions['PT:math'] = array(
'value-type' => 'string',
'validator-factory-callback' => function() {
// load validator builders
$factory = WikibaseRepo::getDefaultValidatorBuilders();
// initialize an array with string validators
// returns an array of validators
// that add basic string validation such as preventing empty strings
$validators = $factory->buildStringValidators();
$validators[] = new MathValidator();
return $validators;
},
'parser-factory-callback' => function( ParserOptions $options ) {
$repo = WikibaseRepo::getDefaultInstance();
$normalizer = new WikibaseStringValueNormalizer( $repo->getStringNormalizer() );
return new StringParser( $normalizer );
},
'formatter-factory-callback' => function( $format, FormatterOptions $options ) {
global $wgOut;
$styles = array( 'ext.math.desktop.styles', 'ext.math.scripts', 'ext.math.styles' );
$wgOut->addModuleStyles( $styles );
return new MathFormatter( $format );
},
);
}
/*
* Add Datatype "Math" to the Wikibase Client
*/
public static function onWikibaseClientDataTypes( array &$dataTypeDefinitions ) {
$dataTypeDefinitions['PT:math'] = array(
'value-type' => 'string',
'formatter-factory-callback' => function( $format, FormatterOptions $options ) {
global $wgOut;
$styles = array( 'ext.math.desktop.styles', 'ext.math.scripts', 'ext.math.styles' );
$wgOut->addModuleStyles( $styles );
return new MathFormatter( $format );
},
);
}
}

View file

@ -24,7 +24,10 @@
"MathInputCheckTexvc": "MathInputCheckTexvc.php",
"MathInputCheckRestbase": "MathInputCheckRestbase.php",
"SpecialMathShowImage": "SpecialMathShowImage.php",
"SpecialMathStatus": "SpecialMathStatus.php"
"SpecialMathStatus": "SpecialMathStatus.php",
"MathValidator": "MathValidator.php",
"MathFormatter": "MathFormatter.php",
"MathWikidataHook": "MathWikidataHook.php"
},
"DefaultUserOptions": {
"math": "png"
@ -57,6 +60,12 @@
],
"EditPageBeforeEditToolbar": [
"MathHooks::onEditPageBeforeEditToolbar"
],
"WikibaseClientDataTypes": [
"MathWikidataHook::onWikibaseClientDataTypes"
],
"WikibaseRepoDataTypes": [
"MathWikidataHook::onWikibaseRepoDataTypes"
]
},
"config": {

100
tests/MathFormatterTest.php Normal file
View file

@ -0,0 +1,100 @@
<?php
/**
* Test the results of MathFormatter
*
* @group Math
*/
use DataValues\StringValue;
use DataValues\NumberValue;
class MathFormatterTest extends MediaWikiTestCase {
const SOME_TEX = "a^2+b^2=c^2";
const FORMAT_PLAIN = 'text/plain';
const FORMAT_HTML = 'text/html';
const FORMAT_XWIKI = 'text/x-wiki';
const FORMAT_UNKNOWN = 'unknown/unknown';
const FORMAT_VALUE = "";
protected static $hasRestbase;
public static function setUpBeforeClass() {
$rbi = new MathRestbaseInterface();
self::$hasRestbase = $rbi->checkBackend( true );
}
/**
* Sets up the fixture, for example, opens a network connection.
* This method is called before a test is executed.
*/
protected function setUp() {
parent::setUp();
if ( !self::$hasRestbase ) {
$this->markTestSkipped( "Can not connect to Restbase Math interface." );
}
}
protected function tearDown() {
parent::tearDown();
}
/**
* Checks the
* @covers MathFormatter::__construct()
*/
public function testBasics() {
$formatter = new MathFormatter( self::FORMAT_PLAIN );
// check if the format input was corretly passed to the class
$this->assertEquals( self::FORMAT_PLAIN, $formatter->getFormat(), 'test getFormat' );
}
/**
* @expectedException ValueFormatters\Exceptions\MismatchingDataValueTypeException
*/
public function testNotStringValue() {
$formatter = new MathFormatter( self::FORMAT_PLAIN );
$formatter->format( new NumberValue( 0 ) );
}
/**
* @expectedException ValueFormatters\Exceptions\MismatchingDataValueTypeException
*/
public function testNullValue() {
$formatter = new MathFormatter( self::FORMAT_PLAIN );
$formatter->format( null );
}
/**
* @expectedException InvalidArgumentException
*/
public function testUnknownFormat() {
$formatter = new MathFormatter( self::FORMAT_UNKNOWN );
}
public function testFormatPlain() {
$formatter = new MathFormatter( self::FORMAT_PLAIN );
$value = new StringValue( self::SOME_TEX );
$resultFormat = $formatter->format( $value );
$this->assertEquals( self::SOME_TEX, $resultFormat,
'Results should be equal' );
}
public function testFormatHtml() {
$formatter = new MathFormatter( self::FORMAT_HTML );
$value = new StringValue( self::SOME_TEX );
$resultFormat = $formatter->format( $value );
$this->assertContains( '</math>', $resultFormat,
'Result must contain math-tag' );
}
public function testFormatXWiki() {
$tex = self::SOME_TEX;
$formatter = new MathFormatter( self::FORMAT_XWIKI );
$value = new StringValue( self::SOME_TEX );
$resultFormat = $formatter->format( $value );
$this->assertEquals( "<math>$tex</math>", $resultFormat,
'Tex wasn\'t properly wrapped' );
}
}

View file

@ -0,0 +1,69 @@
<?php
/**
* Test the results of MathFormatter
*
* @group Math
*/
use DataValues\StringValue;
use DataValues\NumberValue;
class MathValidatorTest extends MediaWikiTestCase {
const VADLID_TEX = "a^2+b^2=c^2";
const INVADLID_TEX = "\\notExists";
protected static $hasRestbase;
public static function setUpBeforeClass() {
$rbi = new MathRestbaseInterface();
self::$hasRestbase = $rbi->checkBackend( true );
}
/**
* Sets up the fixture, for example, opens a network connection.
* This method is called before a test is executed.
*/
protected function setUp() {
parent::setUp();
if ( !self::$hasRestbase ) {
$this->markTestSkipped( "Can not connect to Restbase Math interface." );
}
}
protected function tearDown() {
parent::tearDown();
}
/**
* @expectedException ValueFormatters\Exceptions\MismatchingDataValueTypeException
*/
public function testNotStringValue() {
$validator = new MathValidator();
$validator->validate( new NumberValue( 0 ) );
}
/**
* @expectedException ValueFormatters\Exceptions\MismatchingDataValueTypeException
*/
public function testNullValue() {
$validator = new MathValidator();
$validator->validate( null );
}
public function testValidInput() {
$validator = new MathValidator();
$result = $validator->validate( new StringValue( self::VADLID_TEX ) );
// not supported by jenkins php version
// $this->assertType( \ValueValidators\Result::class, $result );
$this->assertTrue( $result->isValid() );
}
public function testInvalidInput() {
$validator = new MathValidator();
$result = $validator->validate( new StringValue( self::INVADLID_TEX ) );
// not supported by jenkins php version
// $this->assertType( \ValueValidators\Result::class, $result );
$this->assertFalse( $result->isValid() );
}
}