MediaWiki 1.43.1
ParsoidParser.php
Go to the documentation of this file.
1<?php
2
4
20use Wikimedia\Assert\Assert;
21use Wikimedia\Parsoid\Config\PageConfig;
22use Wikimedia\Parsoid\Parsoid;
23
32class ParsoidParser /* eventually this will extend \Parser */ {
39 public const PARSOID_TITLE_KEY = "parsoid:title-dbkey";
40 private Parsoid $parsoid;
41 private PageConfigFactory $pageConfigFactory;
42 private LanguageConverterFactory $languageConverterFactory;
43 private ParserFactory $legacyParserFactory;
44
51 public function __construct(
52 Parsoid $parsoid,
53 PageConfigFactory $pageConfigFactory,
54 LanguageConverterFactory $languageConverterFactory,
55 ParserFactory $legacyParserFactory
56 ) {
57 $this->parsoid = $parsoid;
58 $this->pageConfigFactory = $pageConfigFactory;
59 $this->languageConverterFactory = $languageConverterFactory;
60 $this->legacyParserFactory = $legacyParserFactory;
61 }
62
70 private function genParserOutput(
71 PageConfig $pageConfig, ParserOptions $options, ?ParserOutput $previousOutput
72 ): ParserOutput {
73 $parserOutput = new ParserOutput();
74
75 // Parsoid itself does not vary output by parser options right now.
76 // But, ensure that any option use by extensions, parser functions,
77 // recursive parses, or (in the unlikely future scenario) Parsoid itself
78 // are recorded as used.
79 $options->registerWatcher( [ $parserOutput, 'recordOption' ] );
80
81 // The enable/disable logic here matches that in Parser::internalParseHalfParsed(),
82 // although __NOCONTENTCONVERT__ is handled internal to Parsoid.
83 //
84 // T349137: It might be preferable to handle __NOCONTENTCONVERT__ here rather than
85 // by inspecting the DOM inside Parsoid. That will come in a separate patch.
86 $htmlVariantLanguage = null;
87 if ( !( $options->getDisableContentConversion() || $options->getInterfaceMessage() ) ) {
88 // NOTES (some of these are TODOs for read views integration)
89 // 1. This html variant conversion is a pre-cache transform. HtmlOutputRendererHelper
90 // has another variant conversion that is a post-cache transform based on the
91 // 'Accept-Language' header. If that header is set, there is really no reason to
92 // do this conversion here. So, eventually, we are likely to either not pass in
93 // the htmlVariantLanguage option below OR disable language conversion from the
94 // wt2html path in Parsoid and this and the Accept-Language variant conversion
95 // both would have to be handled as post-cache transforms.
96 //
97 // 2. Parser.php calls convert() which computes a preferred variant from the
98 // target language. But, we cannot do that unconditionally here because REST API
99 // requests specify the exact variant via the 'Content-Language' header.
100 //
101 // For Parsoid page views, either the callers will have to compute the
102 // preferred variant and set it in ParserOptions OR the REST API will have
103 // to set some other flag indicating that the preferred variant should not
104 // be computed. For now, I am adding a temporary hack, but this should be
105 // replaced with something more sensible (T267067).
106 //
107 // 3. Additionally, Parsoid's callers will have to set targetLanguage in ParserOptions
108 // to mimic the logic in Parser.php (missing right now).
109 $langCode = $pageConfig->getPageLanguageBcp47();
110 if ( $options->getRenderReason() === 'page-view' ) { // TEMPORARY HACK
111 $langFactory = MediaWikiServices::getInstance()->getLanguageFactory();
112 $lang = $langFactory->getLanguage( $langCode );
113 $langConv = $this->languageConverterFactory->getLanguageConverter( $lang );
114 $htmlVariantLanguage = $langFactory->getLanguage( $langConv->getPreferredVariant() );
115 } else {
116 $htmlVariantLanguage = $langCode;
117 }
118 }
119 $oldPageConfig = null;
120 $oldPageBundle = null;
121
122 // T371713: Temporary statistics collection code to determine
123 // feasibility of Parsoid selective update
124 $sampleRate = MediaWikiServices::getInstance()->getMainConfig()->get(
126 );
127 $doSample = ( $sampleRate && mt_rand( 1, $sampleRate ) === 1 );
128 if ( $doSample && $previousOutput !== null && $previousOutput->getCacheRevisionId() ) {
129 // Allow fetching the old wikitext corresponding to the
130 // $previousOutput
131 $oldPageConfig = $this->pageConfigFactory->create(
132 Title::newFromLinkTarget( $pageConfig->getLinkTarget() ),
133 $options->getUserIdentity(),
134 $previousOutput->getCacheRevisionId(),
135 null,
136 $previousOutput->getLanguage(),
137 );
138 $oldPageBundle =
140 $previousOutput
141 );
142 }
143
144 $defaultOptions = [
145 'pageBundle' => true,
146 'wrapSections' => true,
147 'logLinterData' => true,
148 'body_only' => false,
149 'htmlVariantLanguage' => $htmlVariantLanguage,
150 'offsetType' => 'byte',
151 'outputContentVersion' => Parsoid::defaultHTMLVersion(),
152 'previousOutput' => $oldPageBundle,
153 'previousInput' => $oldPageConfig,
154 'sampleStats' => $doSample,
155 'renderReason' => $options->getRenderReason(),
156 ];
157
158 $parserOutput->resetParseStartTime();
159
160 // This can throw ClientError or ResourceLimitExceededException.
161 // Callers are responsible for figuring out how to handle them.
162 $pageBundle = $this->parsoid->wikitext2html(
163 $pageConfig,
164 $defaultOptions,
165 $headers,
166 $parserOutput );
167
168 $parserOutput = PageBundleParserOutputConverter::parserOutputFromPageBundle( $pageBundle, $parserOutput );
169
170 // Record the page title in dbkey form so that post-cache transforms
171 // have access to the title.
172 $parserOutput->setExtensionData(
173 self::PARSOID_TITLE_KEY,
174 Title::newFromLinkTarget( $pageConfig->getLinkTarget() )->getPrefixedDBkey()
175 );
176
177 // Register a watcher again because the $parserOutput arg
178 // and $parserOutput return value above are different objects!
179 $options->registerWatcher( [ $parserOutput, 'recordOption' ] );
180
181 $parserOutput->setFromParserOptions( $options );
182
183 $parserOutput->recordTimeProfile();
184 $this->makeLimitReport( $options, $parserOutput );
185
186 // T371713: Collect statistics on parsing time -vs- presence of
187 // $previousOutput
188 $stats = MediaWikiServices::getInstance()->getStatsFactory();
189 $labels = [
190 'type' => $previousOutput === null ? 'full' : 'selective',
191 'wiki' => WikiMap::getCurrentWikiId(),
192 'reason' => $options->getRenderReason() ?: 'unknown',
193 ];
194 $stats
195 ->getCounter( 'Parsoid_parse_cpu_seconds' )
196 ->setLabels( $labels )
197 ->incrementBy( $parserOutput->getTimeProfile( 'cpu' ) );
198 $stats
199 ->getCounter( 'Parsoid_parse_total' )
200 ->setLabels( $labels )
201 ->increment();
202
203 // Add Parsoid skinning module
204 $parserOutput->addModuleStyles( [ 'mediawiki.skinning.content.parsoid' ] );
205
206 // Record Parsoid version in extension data; this allows
207 // us to use the onRejectParserCacheValue hook to selectively
208 // expire "bad" generated content in the event of a rollback.
209 $parserOutput->setExtensionData(
210 'core:parsoid-version', Parsoid::version()
211 );
212 $parserOutput->setExtensionData(
213 'core:html-version', Parsoid::defaultHTMLVersion()
214 );
215
216 return $parserOutput;
217 }
218
239 public function parse(
240 $text, PageReference $page, ParserOptions $options,
241 bool $linestart = true, bool $clearState = true, ?int $revId = null,
242 ?ParserOutput $previousOutput = null
243 ): ParserOutput {
244 Assert::invariant( $linestart, '$linestart=false is not yet supported' );
245 Assert::invariant( $clearState, '$clearState=false is not yet supported' );
246 $title = Title::newFromPageReference( $page );
247 $lang = $options->getTargetLanguage();
248 if ( $lang === null && $options->getInterfaceMessage() ) {
249 $lang = $options->getUserLangObj();
250 }
251 $pageConfig = $revId === null || $revId === 0 ? null : $this->pageConfigFactory->create(
252 $title,
253 $options->getUserIdentity(),
254 $revId,
255 null, // unused
256 $lang // defaults to title page language if null
257 );
258 $content = null;
259 if ( $text instanceof TextContent ) {
260 $content = $text;
261 $text = $content->getText();
262 }
263 if ( !( $pageConfig && $pageConfig->getPageMainContent() === $text ) ) {
264 // This is a bit awkward! But we really need to parse $text, which
265 // may or may not correspond to the $revId provided!
266 // T332928 suggests one solution: splitting the "have revid"
267 // callers from the "bare text, no associated revision" callers.
268 $revisionRecord = new MutableRevisionRecord( $title );
269 if ( $revId !== null ) {
270 $revisionRecord->setId( $revId );
271 }
272 $revisionRecord->setSlot(
273 SlotRecord::newUnsaved(
274 SlotRecord::MAIN,
275 $content ?? new WikitextContent( $text )
276 )
277 );
278 $pageConfig = $this->pageConfigFactory->create(
279 $title,
280 $options->getUserIdentity(),
281 $revisionRecord,
282 null, // unused
283 $lang // defaults to title page language if null
284 );
285 }
286
287 return $this->genParserOutput( $pageConfig, $options, $previousOutput );
288 }
289
302 public function parseFakeRevision(
303 RevisionRecord $fakeRev, PageReference $page, ParserOptions $options
304 ): ParserOutput {
305 wfDeprecated( __METHOD__, '1.43' );
306 $title = Title::newFromPageReference( $page );
307 $lang = $options->getTargetLanguage();
308 if ( $lang === null && $options->getInterfaceMessage() ) {
309 $lang = $options->getUserLangObj();
310 }
311 $pageConfig = $this->pageConfigFactory->create(
312 $title,
313 $options->getUserIdentity(),
314 $fakeRev,
315 null, // unused
316 $lang // defaults to title page language if null
317 );
318
319 return $this->genParserOutput( $pageConfig, $options, null );
320 }
321
327 protected function makeLimitReport(
328 ParserOptions $parserOptions, ParserOutput $parserOutput
329 ) {
330 $maxIncludeSize = $parserOptions->getMaxIncludeSize();
331
332 $cpuTime = $parserOutput->getTimeProfile( 'cpu' );
333 if ( $cpuTime !== null ) {
334 $parserOutput->setLimitReportData( 'limitreport-cputime',
335 sprintf( "%.3f", $cpuTime )
336 );
337 }
338
339 $wallTime = $parserOutput->getTimeProfile( 'wall' );
340 $parserOutput->setLimitReportData( 'limitreport-walltime',
341 sprintf( "%.3f", $wallTime )
342 );
343
344 $parserOutput->setLimitReportData( 'limitreport-timingprofile', [ 'not yet supported' ] );
345
346 // Add other cache related metadata
347 $parserOutput->setLimitReportData( 'cachereport-timestamp',
348 $parserOutput->getCacheTime() );
349 $parserOutput->setLimitReportData( 'cachereport-ttl',
350 $parserOutput->getCacheExpiry() );
351 $parserOutput->setLimitReportData( 'cachereport-transientcontent',
352 $parserOutput->hasReducedExpiry() );
353 }
354
355}
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
Content object implementation for representing flat text.
Content object for wiki text pages.
An interface for creating language converters.
A class containing constants representing the names of configuration variables.
const ParsoidSelectiveUpdateSampleRate
Name constant for the ParsoidSelectiveUpdateSampleRate setting, for use with Config::get()
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
getCacheExpiry()
Returns the number of seconds after which this object should expire.
Set options of the Parser.
getMaxIncludeSize()
Maximum size of template expansions, in bytes.
getDisableContentConversion()
Whether content conversion should be disabled.
getRenderReason()
Returns reason for rendering the content.
getInterfaceMessage()
Parsing an interface message?
getTargetLanguage()
Target language for the parse.
getUserIdentity()
Get the identity of the user for whom the parse is made.
getUserLangObj()
Get the user language used by the parser for this page and split the parser cache.
registerWatcher( $callback)
Registers a callback for tracking which ParserOptions which are used.
ParserOutput is a rendering of a Content object or a message.
setLimitReportData( $key, $value)
Sets parser limit report data for a key.
getTimeProfile(string $clock)
Returns the time that elapsed between the most recent call to resetParseStartTime() and the first cal...
hasReducedExpiry()
Check whether the cache TTL was lowered from the site default.
getLanguage()
Get the primary language code of the output.
Helper class used by MediaWiki to create Parsoid PageConfig objects.
static pageBundleFromParserOutput(ParserOutput $parserOutput)
Returns a Parsoid PageBundle equivalent to the given ParserOutput.
static parserOutputFromPageBundle(PageBundle $pageBundle, ?ParserOutput $originalParserOutput=null)
Creates a ParserOutput object containing the relevant data from the given PageBundle object.
Parser implementation which uses Parsoid.
parse( $text, PageReference $page, ParserOptions $options, bool $linestart=true, bool $clearState=true, ?int $revId=null, ?ParserOutput $previousOutput=null)
Convert wikitext to HTML Do not call this function recursively.
parseFakeRevision(RevisionRecord $fakeRev, PageReference $page, ParserOptions $options)
__construct(Parsoid $parsoid, PageConfigFactory $pageConfigFactory, LanguageConverterFactory $languageConverterFactory, ParserFactory $legacyParserFactory)
makeLimitReport(ParserOptions $parserOptions, ParserOutput $parserOutput)
Set the limit report data in the current ParserOutput.
Page revision base class.
Value object representing a content slot associated with a page revision.
Represents a title within MediaWiki.
Definition Title.php:78
Tools for dealing with other locally-hosted wikis.
Definition WikiMap.php:31
Interface for objects (potentially) representing a page that can be viewable and linked to on a wiki.
Copyright (C) 2011-2022 Wikimedia Foundation and others.