From ea4473037d0cad16286b80bb6c5bafe06c4dafd8 Mon Sep 17 00:00:00 2001 From: daniel Date: Mon, 5 Nov 2012 16:53:48 +0100 Subject: [PATCH] Content::convert() for conv. betw. content models. This is needed to fix bug 41706 and similar, watch for follow-ups. Automatic, implicit conversion may be handy in several cases, especially for converting between different text based content models. E.g. it should be possible to create a diff between a JavaScript and a wikitext page. This change lais the foundations for this ability. Change-Id: Ie7d87b67b24ac9897cb5696220a7785b228d3c79 --- docs/hooks.txt | 10 ++++ includes/content/AbstractContent.php | 26 +++++++++++ includes/content/Content.php | 17 ++++++- includes/content/TextContent.php | 46 +++++++++++++++++-- .../includes/content/TextContentTest.php | 45 ++++++++++++++++++ 5 files changed, 139 insertions(+), 5 deletions(-) diff --git a/docs/hooks.txt b/docs/hooks.txt index 998523f334..03ee6c54a0 100644 --- a/docs/hooks.txt +++ b/docs/hooks.txt @@ -752,6 +752,16 @@ cointent model name, but no entry for that model exists in $wgContentHandlers. $modeName: the requested content model name &$handler: set this to a ContentHandler object, if desired. +'ConvertContent': Called by AbstractContent::convert when a conversion to another +content model is requested. +$content: The Content object to be converted. +$toModel: The ID of the content model to convert to. +$lossy: boolean indicating whether lossy conversion is allowed. +&$result: Output parameter, in case the handler function wants to provide a +converted Content object. Note that $result->getContentModel() must return $toModel. +Handler functions that modify $result should generally return false to further +attempts at conversion. + 'ContribsPager::getQueryInfo': Before the contributions query is about to run &$pager: Pager object for contributions &$queryInfo: The query for the contribs Pager diff --git a/includes/content/AbstractContent.php b/includes/content/AbstractContent.php index 0a8bb9e997..386f55aee5 100644 --- a/includes/content/AbstractContent.php +++ b/includes/content/AbstractContent.php @@ -411,4 +411,30 @@ abstract class AbstractContent implements Content { public function matchMagicWord( MagicWord $word ) { return false; } + + /** + * @see Content::convert() + * + * This base implementation calls the hook ConvertContent to enable custom conversions. + * Subclasses may override this to implement conversion for "their" content model. + * + * @param String $toModel the desired content model, use the CONTENT_MODEL_XXX flags. + * @param String $lossy flag, set to "lossy" to allow lossy conversion. If lossy conversion is + * not allowed, full round-trip conversion is expected to work without losing information. + * + * @return Content|bool A content object with the content model $toModel, or false if + * that conversion is not supported. + */ + public function convert( $toModel, $lossy = '' ) { + if ( $this->getModel() === $toModel ) { + //nothing to do, shorten out. + return $this; + } + + $lossy = ( $lossy === 'lossy' ); // string flag, convert to boolean for convenience + $result = false; + + wfRunHooks( 'ConvertContent', array( $this, $toModel, $lossy, &$result ) ); + return $result; + } } diff --git a/includes/content/Content.php b/includes/content/Content.php index 3c776941bc..35b51c346f 100644 --- a/includes/content/Content.php +++ b/includes/content/Content.php @@ -42,7 +42,7 @@ interface Content { /** * @since 1.21 * - * @return string The wikitext to include when another page includes this + * @return string|false The wikitext to include when another page includes this * content, or false if the content is not includable in a wikitext page. * * @todo allow native handling, bypassing wikitext representation, like @@ -481,7 +481,20 @@ interface Content { */ public function matchMagicWord( MagicWord $word ); - // TODO: ImagePage and CategoryPage interfere with per-content action handlers + /** + * Converts this content object into another content object with the given content model, + * if that is possible. + * + * @param String $toModel the desired content model, use the CONTENT_MODEL_XXX flags. + * @param String $lossy flag, set to "lossy" to allow lossy conversion. If lossy conversion is + * not allowed, full round-trip conversion is expected to work without losing information. + * + * @return Content|bool A content object with the content model $toModel, or false if + * that conversion is not supported. + */ + public function convert( $toModel, $lossy = '' ); + + // TODO: ImagePage and CategoryPage interfere with per-content action handlers // TODO: nice&sane integration of GeSHi syntax highlighting // [11:59] Hooks are ugly; make CodeHighlighter interface and a // config to set the class which handles syntax highlighting diff --git a/includes/content/TextContent.php b/includes/content/TextContent.php index 0e22fcef1c..872738b4fe 100644 --- a/includes/content/TextContent.php +++ b/includes/content/TextContent.php @@ -115,12 +115,21 @@ class TextContent extends AbstractContent { } /** - * Returns the text represented by this Content object, as a string. + * Returns attempts to convert this content object to wikitext, + * and then returns the text string. The conversion may be lossy. * - * @return string: the raw text + * @note: this allows any text-based content to be transcluded as if it was wikitext. + * + * @return string|false: the raw text, or null if the conversion failed */ public function getWikitextForTransclusion( ) { - return $this->getNativeData(); + $wikitext = $this->convert( CONTENT_MODEL_WIKITEXT, 'lossy' ); + + if ( $wikitext ) { + return $wikitext->getNativeData(); + } else { + return false; + } } /** @@ -237,4 +246,35 @@ class TextContent extends AbstractContent { # TODO: make Highlighter interface, use highlighter here, if available return htmlspecialchars( $this->getNativeData() ); } + + /** + * @see Content::convert() + * + * This implementation provides lossless conversion between content models based + * on TextContent. + * + * @param String $toModel the desired content model, use the CONTENT_MODEL_XXX flags. + * @param String $lossy flag, set to "lossy" to allow lossy conversion. If lossy conversion is + * not allowed, full round-trip conversion is expected to work without losing information. + * + * @return Content|bool A content object with the content model $toModel, or false if + * that conversion is not supported. + */ + public function convert( $toModel, $lossy = '' ) { + $converted = parent::convert( $toModel, $lossy ); + + if ( $converted !== false ) { + return $converted; + } + + $toHandler = ContentHandler::getForModelID( $toModel ); + + if ( $toHandler instanceof TextContentHandler ) { + //NOTE: ignore content serialization format - it's just text anyway. + $text = $this->getNativeData(); + $converted = $toHandler->unserializeContent( $text ); + } + + return $converted; + } } diff --git a/tests/phpunit/includes/content/TextContentTest.php b/tests/phpunit/includes/content/TextContentTest.php index c867a83e2e..337945db53 100644 --- a/tests/phpunit/includes/content/TextContentTest.php +++ b/tests/phpunit/includes/content/TextContentTest.php @@ -378,4 +378,49 @@ class TextContentTest extends MediaWikiTestCase { } } + public static function provideConvert() { + return array( + array( // #0 + 'Hallo Welt', + CONTENT_MODEL_WIKITEXT, + 'lossless', + 'Hallo Welt' + ), + array( // #1 + 'Hallo Welt', + CONTENT_MODEL_WIKITEXT, + 'lossless', + 'Hallo Welt' + ), + array( // #1 + 'Hallo Welt', + CONTENT_MODEL_CSS, + 'lossless', + 'Hallo Welt' + ), + array( // #1 + 'Hallo Welt', + CONTENT_MODEL_JAVASCRIPT, + 'lossless', + 'Hallo Welt' + ), + ); + } + + /** + * @dataProvider provideConvert + */ + public function testConvert( $text, $model, $lossy, $expectedNative ) { + $content = $this->newContent( $text ); + + $converted = $content->convert( $model, $lossy ); + + if ( $expectedNative === false ) { + $this->assertFalse( $converted, "conversion to $model was expected to fail!" ); + } else { + $this->assertInstanceOf( 'Content', $converted ); + $this->assertEquals( $expectedNative, $converted->getNativeData() ); + } + } + } -- 2.20.1