20 namespace MediaWiki\Parser\Parsoid;
25 use InvalidArgumentException;
26 use Liuggio\StatsdClient\Factory\StatsdDataFactory;
46 use Wikimedia\Parsoid\Config\PageConfig;
47 use Wikimedia\Parsoid\Config\SiteConfig;
48 use Wikimedia\Parsoid\Core\ClientError;
49 use Wikimedia\Parsoid\Core\ResourceLimitExceededException;
50 use Wikimedia\Parsoid\Parsoid;
63 public const PARSOID_PARSER_CACHE_NAME =
'parsoid';
68 private const RENDER_ID_KEY =
'parsoid-render-id';
71 public const OPT_FORCE_PARSE = 1;
76 public const OPT_NO_UPDATE_CACHE = 2;
81 public const OPT_LOG_LINT_DATA = 64;
90 private $revisionOutputCache;
96 private $globalIdGenerator;
102 private $parsoidCacheConfig;
108 private $parsoidPageConfigFactory;
114 private $revisionLookup;
123 private $parsoidWikiId;
126 private $contentHandlerFactory;
141 ServiceOptions $options,
143 PageLookup $pageLookup,
144 RevisionLookup $revisionLookup,
145 GlobalIdGenerator $globalIdGenerator,
148 SiteConfig $siteConfig,
149 PageConfigFactory $parsoidPageConfigFactory,
150 IContentHandlerFactory $contentHandlerFactory
152 $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
153 $this->options = $options;
155 $this->revisionOutputCache = $parserCacheFactory
157 $this->parserCache = $parserCacheFactory->
getParserCache( self::PARSOID_PARSER_CACHE_NAME );
158 $this->pageLookup = $pageLookup;
159 $this->revisionLookup = $revisionLookup;
160 $this->globalIdGenerator = $globalIdGenerator;
161 $this->stats = $stats;
162 $this->parsoid = $parsoid;
163 $this->siteConfig = $siteConfig;
164 $this->parsoidPageConfigFactory = $parsoidPageConfigFactory;
165 $this->contentHandlerFactory = $contentHandlerFactory;
171 $this->parsoidWikiId = $options->get(
'ParsoidWikiID' );
188 $handler = $this->contentHandlerFactory->getContentHandler( $model );
197 return $this->siteConfig->getContentModelHandler( $model ) !==
null;
214 [ $page, $revision ] = $this->resolveRevision( $page, $revision );
215 $isOld = $revision->getId() !== $page->getLatest();
217 $statsKey = $isOld ?
'ParsoidOutputAccess.Cache.revision' :
'ParsoidOutputAccess.Cache.parser';
219 if ( !( $options & self::OPT_FORCE_PARSE ) ) {
220 $parserOutput = $this->getCachedParserOutputInternal(
228 if ( $parserOutput ) {
233 $parsoidOptions = [];
235 if ( $options & self::OPT_LOG_LINT_DATA ) {
237 'logLinterData' =>
true
243 $startTime = microtime(
true );
244 $status = $this->
parse( $page, $parserOpts, $parsoidOptions, $revision );
245 $time = microtime(
true ) - $startTime;
247 if ( !$status->isOK() ) {
248 $this->stats->increment( $statsKey .
'.save.notok' );
249 } elseif ( $options & self::OPT_NO_UPDATE_CACHE ) {
250 $this->stats->increment( $statsKey .
'.save.disabled' );
251 } elseif ( !$this->supportsContentModel( $mainSlot->getModel() ) ) {
256 $this->stats->increment( $statsKey .
'.save.badmodel' );
258 if ( $time > $this->parsoidCacheConfig->get(
'CacheThresholdTime' ) ) {
259 $parserOutput = $status->getValue();
263 $this->revisionOutputCache->save( $parserOutput, $revision, $parserOpts, $now );
265 $this->parserCache->save( $parserOutput, $page, $parserOpts, $now );
267 $this->stats->increment( $statsKey .
'.save.ok' );
269 $this->stats->increment( $statsKey .
'.save.skipfast' );
282 private function parseInternal(
283 PageConfig $pageConfig,
284 array $parsoidOptions
287 'pageBundle' =>
true,
288 'prefix' => $this->parsoidWikiId,
289 'pageName' => $pageConfig->
getTitle(),
290 'htmlVariantLanguage' => $pageConfig->getPageLanguage(),
291 'outputContentVersion' => Parsoid::defaultHTMLVersion(),
295 $startTime = microtime(
true );
296 $pageBundle = $this->parsoid->wikitext2html(
298 $parsoidOptions + $defaultOptions
301 $parserOutput = PageBundleParserOutputConverter::parserOutputFromPageBundle( $pageBundle );
302 $time = microtime(
true ) - $startTime;
304 LoggerFactory::getInstance(
'slow-parsoid' )
305 ->info(
'Parsing {title} was slow, took {time} seconds', [
306 'time' => number_format( $time, 2 ),
307 'title' => $pageConfig->getTitle(),
311 }
catch ( ClientError $e ) {
313 }
catch ( ResourceLimitExceededException $e ) {
314 return Status::newFatal(
'parsoid-resource-limit-exceeded', $e->getMessage() );
331 throw new InvalidArgumentException(
'ParserOutput does not have a render ID' );
334 return ParsoidRenderID::newFromKey( $renderId );
349 [ $page, $revision ] = $this->resolveRevision( $page, $revision );
350 $isOld = $revision->getId() !== $page->getLatest();
352 $statsKey = $isOld ?
'ParsoidOutputAccess.Cache.revision' :
'ParsoidOutputAccess.Cache.parser';
354 return $this->getCachedParserOutputInternal(
375 ?RevisionRecord $revision,
380 $parserOutput = $this->revisionOutputCache->get( $revision, $parserOpts );
382 $parserOutput = $this->parserCache->get( $page, $parserOpts );
385 if ( $parserOutput ) {
388 if ( !$parserOutput->
getExtensionData( PageBundleParserOutputConverter::PARSOID_PAGE_BUNDLE_KEY )
391 $parserOutput =
null;
395 if ( $parserOutput ) {
396 $this->stats->increment( $statsKey .
'.get.hit' );
397 return $parserOutput;
399 $this->stats->increment( $statsKey .
'.get.miss' );
404 private function makeDummyParserOutput(
string $contentModel ):
Status {
405 $msg =
"Dummy output. Parsoid does not support content model $contentModel. See T324711.";
409 $output->updateCacheExpiry( 0 );
411 $output->setExtensionData( self::RENDER_ID_KEY,
'0/dummy-output' );
427 array $parsoidOptions,
432 if ( !$revision instanceof RevisionRecord ) {
433 [ $page, $revision ] = $this->resolveRevision( $page, $revision );
436 $mainSlot = $revision->getSlot( SlotRecord::MAIN );
437 $contentModel = $mainSlot->getModel();
438 if ( !$this->supportsContentModel( $contentModel ) ) {
441 return $this->makeDummyParserOutput( $contentModel );
453 $langCode = $languageOverride ? $languageOverride->getCode() :
null;
454 $pageConfig = $this->parsoidPageConfigFactory->create(
460 $this->options->get( MainConfigNames::ParsoidSettings )
463 $status = $this->parseInternal( $pageConfig, $parsoidOptions );
465 if ( !$status->isOK() ) {
469 $parserOutput = $status->getValue();
474 $revId = $revision->getId();
475 $parsoidRenderId =
new ParsoidRenderID( $revId, $this->globalIdGenerator->newUUIDv1() );
476 $parserOutput->
setExtensionData( self::RENDER_ID_KEY, $parsoidRenderId->getKey() );
492 private function resolveRevision( PageIdentity $page, $revision ): array {
493 if ( !$page instanceof PageRecord ) {
495 $page = $this->pageLookup->getPageByReference( $page );
497 throw new RevisionAccessException(
498 'Page {name} not found',
504 if ( $revision ===
null ) {
505 $revision = $page->getLatest();
508 if ( is_int( $revision ) ) {
510 $revision = $this->revisionLookup->getRevisionById( $revId );
513 throw new RevisionAccessException(
514 'Revision {revId} not found',
515 [
'revId' => $revId ]
520 return [ $page, $revision ];
const CONTENT_MODEL_WIKITEXT
const CONTENT_FORMAT_WIKITEXT
Wikitext.
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
getExtensionData()
Get the extension data as: augmentor name => data.
if(!defined('MW_SETUP_CALLBACK'))
The persistent session ID (if any) loaded at startup.
setCacheTime( $t)
setCacheTime() sets the timestamp expressing when the page has been rendered.
A Config instance which stores all settings as a member variable.
Exception thrown when an unregistered content model is requested.
A class containing constants representing the names of configuration variables.
const ParsoidCacheConfig
Name constant for the ParsoidCacheConfig setting, for use with Config::get()
const ParsoidSettings
Name constant for the ParsoidSettings setting, for use with Config::get()
Helper class used by MediaWiki to create Parsoid PageConfig objects.
Cache for ParserOutput objects corresponding to the latest page revisions.
Set options of the Parser.
getTargetLanguage()
Target language for the parse.
getExtensionData( $key)
Gets extensions data previously attached to this ParserOutput using setExtensionData().
setExtensionData( $key, $value)
Attaches arbitrary data to this ParserObject.
parse( $text, PageReference $page, ParserOptions $options, $linestart=true, $clearState=true, $revid=null)
Convert wikitext to HTML Do not call this function recursively.
static newFatal( $message,... $parameters)
Factory function for fatal errors.
static newGood( $value=null)
Factory function for good results.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Interface for configuration instances.
MediaWiki adaptation of StatsdDataFactory that provides buffering functionality.
Interface for objects (potentially) representing an editable wiki page.
Service for looking up information about wiki pages.