Moved getParserOutput to Content interface.

On Tim's request, this change moved getParserOutput() and getSecondaryDataUpdates()
from the ContentHandler to the Content interface.

Change-Id: Ia654aa8710a242ba5fe7a4eb528e6a6449035f59
This commit is contained in:
daniel 2012-07-23 23:52:34 +02:00
parent c8e633f1e3
commit 8d280dde9b
10 changed files with 270 additions and 300 deletions

View file

@ -221,26 +221,62 @@ interface Content {
*/
public function isCountable( $hasLinks = null ) ;
/**
* Convenience method, shorthand for
* $this->getContentHandler()->getParserOutput( $this, $title, $revId, $options, $generateHtml )
* Parse the Content object and generate a ParserOutput from the result.
* $result->getText() can be used to obtain the generated HTML. If no HTML
* is needed, $generateHtml can be set to false; in that case,
* $result->getText() may return null.
*
* @note: subclasses should NOT override this to provide custom rendering.
* Override ContentHandler::getParserOutput() instead!
*
* @param $title Title
* @param $revId null
* @param $options null|ParserOptions
* @param $generateHtml Boolean Whether to generate HTML (default: true).
* If false, the result of calling getText() on the ParserOutput object
* returned by this method is undefined.
* @param $title Title The page title to use as a context for rendering
* @param $revId null|int The revision being rendered (optional)
* @param $options null|ParserOptions Any parser options
* @param $generateHtml Boolean Whether to generate HTML (default: true). If false,
* the result of calling getText() on the ParserOutput object returned by
* this method is undefined.
*
* @since WD.1
*
* @return ParserOutput
*/
public function getParserOutput( Title $title, $revId = null, ParserOptions $options = null,
$generateHtml = true );
public function getParserOutput( Title $title,
$revId = null,
ParserOptions $options = null, $generateHtml = true );
# TODO: make RenderOutput and RenderOptions base classes
/**
* Returns a list of DataUpdate objects for recording information about this
* Content in some secondary data store. If the optional second argument,
* $old, is given, the updates may model only the changes that need to be
* made to replace information about the old content with information about
* the new content.
*
* This default implementation calls
* $this->getParserOutput( $content, $title, null, null, false ),
* and then calls getSecondaryDataUpdates( $title, $recursive ) on the
* resulting ParserOutput object.
*
* Subclasses may implement this to determine the necessary updates more
* efficiently, or make use of information about the old content.
*
* @param $title Title The context for determining the necessary updates
* @param $old Content|null An optional Content object representing the
* previous content, i.e. the content being replaced by this Content
* object.
* @param $recursive boolean Whether to include recursive updates (default:
* false).
* @param $parserOutput ParserOutput|null Optional ParserOutput object.
* Provide if you have one handy, to avoid re-parsing of the content.
*
* @return Array. A list of DataUpdate objects for putting information
* about this content object somewhere.
*
* @since WD.1
*/
public function getSecondaryDataUpdates( Title $title,
Content $old = null,
$recursive = true, ParserOutput $parserOutput = null
);
/**
* Construct the redirect destination from this content and return an
@ -549,16 +585,47 @@ abstract class AbstractContent implements Content {
return $this->getNativeData() === $that->getNativeData();
}
/**
* @see Content::getParserOutput()
* Returns a list of DataUpdate objects for recording information about this
* Content in some secondary data store.
*
* This default implementation calls
* $this->getParserOutput( $content, $title, null, null, false ),
* and then calls getSecondaryDataUpdates( $title, $recursive ) on the
* resulting ParserOutput object.
*
* Subclasses may override this to determine the secondary data updates more
* efficiently, preferrably without the need to generate a parser output object.
*
* @see Content::getSecondaryDataUpdates()
*
* @param $title Title The context for determining the necessary updates
* @param $old Content|null An optional Content object representing the
* previous content, i.e. the content being replaced by this Content
* object.
* @param $recursive boolean Whether to include recursive updates (default:
* false).
* @param $parserOutput ParserOutput|null Optional ParserOutput object.
* Provide if you have one handy, to avoid re-parsing of the content.
*
* @return Array. A list of DataUpdate objects for putting information
* about this content object somewhere.
*
* @since WD.1
*/
public function getParserOutput( Title $title, $revId = null, ParserOptions $options = null,
$generateHtml = true )
{
return $this->getContentHandler()->getParserOutput(
$this, $title, $revId, $options, $generateHtml );
public function getSecondaryDataUpdates( Title $title,
Content $old = null,
$recursive = true, ParserOutput $parserOutput = null
) {
if ( !$parserOutput ) {
$parserOutput = $this->getParserOutput( $title, null, null, false );
}
return $parserOutput->getSecondaryDataUpdates( $title, $recursive );
}
/**
* @see Content::getRedirectChain()
*/
@ -810,6 +877,60 @@ abstract class TextContent extends AbstractContent {
return $diff;
}
/**
* Returns a generic ParserOutput object, wrapping the HTML returned by
* getHtml().
*
* @param $title Title Context title for parsing
* @param $revId int|null Revision ID (for {{REVISIONID}})
* @param $options ParserOptions|null Parser options
* @param $generateHtml bool Whether or not to generate HTML
*
* @return ParserOutput representing the HTML form of the text
*/
public function getParserOutput( Title $title,
$revId = null,
ParserOptions $options = null, $generateHtml = true
) {
# Generic implementation, relying on $this->getHtml()
if ( $generateHtml ) {
$html = $this->getHtml();
} else {
$html = '';
}
$po = new ParserOutput( $html );
return $po;
}
/**
* Generates an HTML version of the content, for display. Used by
* getParserOutput() to construct a ParserOutput object.
*
* This default implementation just calls getHighlightHtml(). Content
* models that have another mapping to HTML (as is the case for markup
* languages like wikitext) should override this method to generate the
* appropriate HTML.
*
* @return string An HTML representation of the content
*/
protected function getHtml() {
return $this->getHighlightHtml();
}
/**
* Generates a syntax-highlighted version of the content, as HTML.
* Used by the default implementation of getHtml().
*
* @return string an HTML representation of the content's markup
*/
protected function getHighlightHtml( ) {
# TODO: make Highlighter interface, use highlighter here, if available
return htmlspecialchars( $this->getNativeData() );
}
}
/**
@ -1024,6 +1145,44 @@ class WikitextContent extends TextContent {
return $truncatedtext;
}
/**
* Returns a ParserOutput object resulting from parsing the content's text
* using $wgParser.
*
* @since WD.1
*
* @param $content Content the content to render
* @param $title \Title
* @param $revId null
* @param $options null|ParserOptions
* @param $generateHtml bool
*
* @internal param \IContextSource|null $context
* @return ParserOutput representing the HTML form of the text
*/
public function getParserOutput( Title $title,
$revId = null,
ParserOptions $options = null, $generateHtml = true
) {
global $wgParser;
if ( !$options ) {
$options = new ParserOptions();
}
$po = $wgParser->parse( $this->getNativeData(), $title, $options, true, true, $revId );
return $po;
}
protected function getHtml() {
throw new MWException(
"getHtml() not implemented for wikitext. "
. "Use getParserOutput()->getText()."
);
}
}
/**
@ -1103,6 +1262,15 @@ class JavaScriptContent extends TextContent {
return new JavaScriptContent( $pst );
}
protected function getHtml( ) {
$html = "";
$html .= "<pre class=\"mw-code mw-js\" dir=\"ltr\">\n";
$html .= $this->getHighlightHtml( );
$html .= "\n</pre>\n";
return $html;
}
}
/**
@ -1132,4 +1300,13 @@ class CssContent extends TextContent {
return new CssContent( $pst );
}
protected function getHtml( ) {
$html = "";
$html .= "<pre class=\"mw-code mw-css\" dir=\"ltr\">\n";
$html .= $this->getHighlightHtml( );
$html .= "\n</pre>\n";
return $html;
}
}

View file

@ -766,71 +766,6 @@ abstract class ContentHandler {
return $reason;
}
/**
* Parse the Content object and generate a ParserOutput from the result.
* $result->getText() can be used to obtain the generated HTML. If no HTML
* is needed, $generateHtml can be set to false; in that case,
* $result->getText() may return null.
*
* @param $content Content the content to render
* @param $title Title The page title to use as a context for rendering
* @param $revId null|int The revision being rendered (optional)
* @param $options null|ParserOptions Any parser options
* @param $generateHtml Boolean Whether to generate HTML (default: true). If false,
* the result of calling getText() on the ParserOutput object returned by
* this method is undefined.
*
* @since WD.1
*
* @return ParserOutput
*/
public abstract function getParserOutput( Content $content, Title $title,
$revId = null,
ParserOptions $options = null, $generateHtml = true );
# TODO: make RenderOutput and RenderOptions base classes
/**
* Returns a list of DataUpdate objects for recording information about this
* Content in some secondary data store. If the optional second argument,
* $old, is given, the updates may model only the changes that need to be
* made to replace information about the old content with information about
* the new content.
*
* This default implementation calls
* $this->getParserOutput( $content, $title, null, null, false ),
* and then calls getSecondaryDataUpdates( $title, $recursive ) on the
* resulting ParserOutput object.
*
* Subclasses may implement this to determine the necessary updates more
* efficiently, or make use of information about the old content.
*
* @param $content Content The content for determining the necessary updates
* @param $title Title The context for determining the necessary updates
* @param $old Content|null An optional Content object representing the
* previous content, i.e. the content being replaced by this Content
* object.
* @param $recursive boolean Whether to include recursive updates (default:
* false).
* @param $parserOutput ParserOutput|null Optional ParserOutput object.
* Provide if you have one handy, to avoid re-parsing of the content.
*
* @return Array. A list of DataUpdate objects for putting information
* about this content object somewhere.
*
* @since WD.1
*/
public function getSecondaryDataUpdates( Content $content, Title $title,
Content $old = null,
$recursive = true, ParserOutput $parserOutput = null
) {
if ( !$parserOutput ) {
$parserOutput = $this->getParserOutput( $content, $title, null, null, false );
}
return $parserOutput->getSecondaryDataUpdates( $title, $recursive );
}
/**
* Get the Content object that needs to be saved in order to undo all revisions
* between $undo and $undoafter. Revisions must belong to the same page,
@ -953,71 +888,6 @@ abstract class TextContentHandler extends ContentHandler {
return $mergedContent;
}
/**
* Returns a generic ParserOutput object, wrapping the HTML returned by
* getHtml().
*
* @param $content Content The content to render
* @param $title Title Context title for parsing
* @param $revId int|null Revision ID (for {{REVISIONID}})
* @param $options ParserOptions|null Parser options
* @param $generateHtml bool Whether or not to generate HTML
*
* @return ParserOutput representing the HTML form of the text
*/
public function getParserOutput( Content $content, Title $title,
$revId = null,
ParserOptions $options = null, $generateHtml = true
) {
$this->checkModelID( $content->getModel() );
# Generic implementation, relying on $this->getHtml()
if ( $generateHtml ) {
$html = $this->getHtml( $content );
} else {
$html = '';
}
$po = new ParserOutput( $html );
return $po;
}
/**
* Generates an HTML version of the content, for display. Used by
* getParserOutput() to construct a ParserOutput object.
*
* This default implementation just calls getHighlightHtml(). Content
* models that have another mapping to HTML (as is the case for markup
* languages like wikitext) should override this method to generate the
* appropriate HTML.
*
* @param $content Content The content to render
*
* @return string An HTML representation of the content
*/
protected function getHtml( Content $content ) {
$this->checkModelID( $content->getModel() );
return $this->getHighlightHtml( $content );
}
/**
* Generates a syntax-highlighted version the content, as HTML.
* Used by the default implementation of getHtml().
*
* @param $content Content the content to render
*
* @return string an HTML representation of the content's markup
*/
protected function getHighlightHtml( Content $content ) {
$this->checkModelID( $content->getModel() );
# TODO: make Highlighter interface, use highlighter here, if available
return htmlspecialchars( $content->getNativeData() );
}
}
/**
@ -1039,44 +909,6 @@ class WikitextContentHandler extends TextContentHandler {
return new WikitextContent( '' );
}
/**
* Returns a ParserOutput object resulting from parsing the content's text
* using $wgParser.
*
* @since WD.1
*
* @param $content Content the content to render
* @param $title \Title
* @param $revId null
* @param $options null|ParserOptions
* @param $generateHtml bool
*
* @internal param \IContextSource|null $context
* @return ParserOutput representing the HTML form of the text
*/
public function getParserOutput( Content $content, Title $title,
$revId = null,
ParserOptions $options = null, $generateHtml = true
) {
global $wgParser;
$this->checkModelID( $content->getModel() );
if ( !$options ) {
$options = new ParserOptions();
}
$po = $wgParser->parse( $content->getNativeData(), $title, $options, true, true, $revId );
return $po;
}
protected function getHtml( Content $content ) {
throw new MWException(
"getHtml() not implemented for wikitext. "
. "Use getParserOutput()->getText()."
);
}
/**
* Returns true because wikitext supports sections.
*
@ -1118,15 +950,6 @@ class JavaScriptContentHandler extends TextContentHandler {
public function getPageLanguage( Title $title, Content $content = null ) {
return wfGetLangObj( 'en' );
}
protected function getHtml( Content $content ) {
$html = "";
$html .= "<pre class=\"mw-code mw-js\" dir=\"ltr\">\n";
$html .= $this->getHighlightHtml( $content );
$html .= "\n</pre>\n";
return $html;
}
}
/**
@ -1158,13 +981,4 @@ class CssContentHandler extends TextContentHandler {
public function getPageLanguage( Title $title, Content $content = null ) {
return wfGetLangObj( 'en' );
}
protected function getHtml( Content $content ) {
$html = "";
$html .= "<pre class=\"mw-code mw-css\" dir=\"ltr\">\n";
$html .= $this->getHighlightHtml( $content );
$html .= "\n</pre>\n";
return $html;
}
}

View file

@ -2046,8 +2046,7 @@ class WikiPage extends Page {
}
# Update the links tables and other secondary data
$contentHandler = $revision->getContentHandler();
$updates = $contentHandler->getSecondaryDataUpdates( $content, $this->getTitle(), null, true, $editInfo->output );
$updates = $content->getSecondaryDataUpdates( $this->getTitle(), null, true, $editInfo->output );
DataUpdate::runUpdates( $updates );
wfRunHooks( 'ArticleEditUpdates', array( &$this, &$editInfo, $options['changed'] ) );

View file

@ -96,7 +96,7 @@ class ApiPurge extends ApiBase {
$p_result = $content->getParserOutput( $title, $page->getLatest(), $popts, $wgEnableParserCache );
# Update the links tables
$updates = $content->getContentHandler()->getSecondaryDataUpdates( $content, $title, null, true, $p_result );
$updates = $content->getSecondaryDataUpdates( $title, null, true, $p_result );
DataUpdate::runUpdates( $updates );
$r['linkupdate'] = '';

View file

@ -63,7 +63,7 @@ class RefreshLinksJob extends Job {
wfProfileOut( __METHOD__.'-parse' );
wfProfileIn( __METHOD__.'-update' );
$updates = $content->getContentHandler()->getSecondaryDataUpdates( $content, $this->title, null, false, $parserOutput );
$updates = $content->getSecondaryDataUpdates( $this->title, null, false, $parserOutput );
DataUpdate::runUpdates( $updates );
wfProfileOut( __METHOD__.'-update' );
@ -139,7 +139,7 @@ class RefreshLinksJob2 extends Job {
wfProfileOut( __METHOD__.'-parse' );
wfProfileIn( __METHOD__.'-update' );
$updates = $content->getContentHandler()->getSecondaryDataUpdates( $content, $title, null, false, $parserOutput );
$updates = $content->getSecondaryDataUpdates( $title, null, false, $parserOutput );
DataUpdate::runUpdates( $updates );
wfProfileOut( __METHOD__.'-update' );

View file

@ -218,8 +218,7 @@ class RefreshLinks extends Maintenance {
$dbw = wfGetDB( DB_MASTER );
$dbw->begin( __METHOD__ );
$contentHandler = $content->getContentHandler();
$updates = $contentHandler->getSecondaryDataUpdates( $content, $page->getTitle() );
$updates = $content->getSecondaryDataUpdates( $page->getTitle() );
DataUpdate::runUpdates( $updates );
$dbw->commit( __METHOD__ );

View file

@ -231,71 +231,6 @@ class ContentHandlerTest extends MediaWikiTestCase {
}
public function dataGetParserOutput() {
return array(
array("ContentHandlerTest_testGetParserOutput", "hello ''world''\n", "<p>hello <i>world</i>\n</p>"),
// @todo: more...?
);
}
/**
* @dataProvider dataGetParserOutput
*/
public function testGetParserOutput( $title, $text, $expectedHtml ) {
$title = Title::newFromText( $title );
$handler = ContentHandler::getForModelID( $title->getContentModel() );
$content = ContentHandler::makeContent( $text, $title );
$po = $handler->getParserOutput( $content, $title );
$this->assertEquals( $expectedHtml, $po->getText() );
// @todo: assert more properties
}
public function dataGetSecondaryDataUpdates() {
return array(
array("ContentHandlerTest_testGetSecondaryDataUpdates_1", "hello ''world''\n",
array( 'LinksUpdate' => array( 'mRecursive' => true,
'mLinks' => array() ) )
),
array("ContentHandlerTest_testGetSecondaryDataUpdates_2", "hello [[world test 21344]]\n",
array( 'LinksUpdate' => array( 'mRecursive' => true,
'mLinks' => array( array( 'World_test_21344' => 0 ) ) ) )
),
// @todo: more...?
);
}
/**
* @dataProvider dataGetSecondaryDataUpdates
*/
public function testGetSecondaryDataUpdates( $title, $text, $expectedStuff ) {
$title = Title::newFromText( $title );
$title->resetArticleID( 2342 ); //dummy id. fine as long as we don't try to execute the updates!
$handler = ContentHandler::getForModelID( $title->getContentModel() );
$content = ContentHandler::makeContent( $text, $title );
$updates = $handler->getSecondaryDataUpdates( $content, $title );
// make updates accessible by class name
foreach ( $updates as $update ) {
$class = get_class( $update );
$updates[ $class ] = $update;
}
foreach ( $expectedStuff as $class => $fieldValues ) {
$this->assertArrayHasKey( $class, $updates, "missing an update of type $class" );
$update = $updates[ $class ];
foreach ( $fieldValues as $field => $value ) {
$v = $update->$field; #if the field doesn't exist, just crash and burn
$this->assertEquals( $value, $v, "unexpected value for field $field in instance of $class" );
}
}
}
public function testSupportsSections() {
$this->markTestIncomplete( "not yet implemented" );
}
@ -340,22 +275,6 @@ class DummyContentHandlerForTesting extends ContentHandler {
{
return new DummyContentForTesting( '' );
}
/**
* @param Content $content
* @param Title $title
* @param null $revId
* @param null|ParserOptions $options
* @param Boolean $generateHtml whether to generate Html (default: true). If false,
* the result of calling getText() on the ParserOutput object returned by
* this method is undefined.
*
* @return ParserOutput
*/
public function getParserOutput( Content $content, Title $title, $revId = null, ParserOptions $options = NULL, $generateHtml = true )
{
return new ParserOutput( $content->getNativeData() );
}
}
class DummyContentForTesting extends AbstractContent {
@ -452,5 +371,20 @@ class DummyContentForTesting extends AbstractContent {
{
return false;
}
/**
* @param Title $title
* @param null $revId
* @param null|ParserOptions $options
* @param Boolean $generateHtml whether to generate Html (default: true). If false,
* the result of calling getText() on the ParserOutput object returned by
* this method is undefined.
*
* @return ParserOutput
*/
public function getParserOutput( Title $title, $revId = null, ParserOptions $options = NULL, $generateHtml = true )
{
return new ParserOutput( $this->getNativeData() );
}
}

View file

@ -12,7 +12,7 @@ class CssContentTest extends JavascriptContentTest {
public function dataGetParserOutput() {
return array(
array("hello <world>\n", "<pre class=\"mw-code mw-css\" dir=\"ltr\">\nhello &lt;world&gt;\n\n</pre>\n"),
array("MediaWiki:Test.css", "hello <world>\n", "<pre class=\"mw-code mw-css\" dir=\"ltr\">\nhello &lt;world&gt;\n\n</pre>\n"),
// @todo: more...?
);
}

View file

@ -12,7 +12,7 @@ class JavascriptContentTest extends WikitextContentTest {
public function dataGetParserOutput() {
return array(
array("hello <world>\n", "<pre class=\"mw-code mw-js\" dir=\"ltr\">\nhello &lt;world&gt;\n\n</pre>\n"),
array("MediaWiki:Test.js", "hello <world>\n", "<pre class=\"mw-code mw-js\" dir=\"ltr\">\nhello &lt;world&gt;\n\n</pre>\n"),
// @todo: more...?
);
}

View file

@ -14,9 +14,10 @@ class WikitextContentTest extends MediaWikiTestCase {
return new WikitextContent( $text );
}
public function dataGetParserOutput() {
return array(
array("hello ''world''\n", "<p>hello <i>world</i>\n</p>"),
array("WikitextContentTest_testGetParserOutput", "hello ''world''\n", "<p>hello <i>world</i>\n</p>"),
// @todo: more...?
);
}
@ -24,15 +25,61 @@ class WikitextContentTest extends MediaWikiTestCase {
/**
* @dataProvider dataGetParserOutput
*/
public function testGetParserOutput( $text, $expectedHtml ) {
$content = $this->newContent( $text );
public function testGetParserOutput( $title, $text, $expectedHtml ) {
$title = Title::newFromText( $title );
$content = ContentHandler::makeContent( $text, $title );
$po = $content->getParserOutput( $this->context->getTitle() );
$po = $content->getParserOutput( $title );
$this->assertEquals( $expectedHtml, $po->getText() );
return $po;
// @todo: assert more properties
}
public function dataGetSecondaryDataUpdates() {
return array(
array("WikitextContentTest_testGetSecondaryDataUpdates_1", "hello ''world''\n",
array( 'LinksUpdate' => array( 'mRecursive' => true,
'mLinks' => array() ) )
),
array("WikitextContentTest_testGetSecondaryDataUpdates_2", "hello [[world test 21344]]\n",
array( 'LinksUpdate' => array( 'mRecursive' => true,
'mLinks' => array( array( 'World_test_21344' => 0 ) ) ) )
),
// @todo: more...?
);
}
/**
* @dataProvider dataGetSecondaryDataUpdates
*/
public function testGetSecondaryDataUpdates( $title, $text, $expectedStuff ) {
$title = Title::newFromText( $title );
$title->resetArticleID( 2342 ); //dummy id. fine as long as we don't try to execute the updates!
$handler = ContentHandler::getForModelID( $title->getContentModel() );
$content = ContentHandler::makeContent( $text, $title );
$updates = $content->getSecondaryDataUpdates( $title );
// make updates accessible by class name
foreach ( $updates as $update ) {
$class = get_class( $update );
$updates[ $class ] = $update;
}
foreach ( $expectedStuff as $class => $fieldValues ) {
$this->assertArrayHasKey( $class, $updates, "missing an update of type $class" );
$update = $updates[ $class ];
foreach ( $fieldValues as $field => $value ) {
$v = $update->$field; #if the field doesn't exist, just crash and burn
$this->assertEquals( $value, $v, "unexpected value for field $field in instance of $class" );
}
}
}
static $sections =
"Intro