36 parent::__construct();
37 $this->
addDescription(
'Compares variant conversion output between Parser and HtmlOutputRendererHelper' );
40 'Name of the page to be parsed and compared',
45 'Target variant language code to transform the content to',
54 $pageName = $this->
getArg(
'page-title' );
55 $pageTitle = Title::newFromText( $pageName );
57 if ( !$pageTitle || !$pageTitle->
exists() ) {
58 $this->
fatalError(
"Title with name $pageName not found" );
61 $targetVariantCode = $this->
getArg(
'target-variant' );
62 $languageNameUtils = $mwInstance->getLanguageNameUtils();
63 if ( !$languageNameUtils->isValidBuiltInCode( $targetVariantCode ) ) {
64 $this->
fatalError(
"$targetVariantCode is not a supported variant" );
66 $targetVariant = $mwInstance->getLanguageFactory()->getLanguage(
70 $user = User::newSystemUser( User::MAINTENANCE_SCRIPT_USER, [
'steal' =>
true ] );
73 $parserOutput = $this->getParserOutput( $pageTitle, $baseLanguage, $targetVariant );
74 $parsoidOutput = $this->getParsoidOutput( $pageTitle, $targetVariant, $user );
75 $converterUsed = $this->getConverterUsed( $parsoidOutput );
77 $this->compareOutput( $parserOutput->getContentHolderText(), $parsoidOutput->
getContentHolderText(),
86 new ServiceOptions( PageRestHelperFactory::CONSTRUCTOR_OPTIONS, $services->getMainConfig() ),
87 $services->getRevisionLookup(),
88 $services->getRevisionRenderer(),
89 $services->getTitleFormatter(),
90 $services->getPageStore(),
91 $services->getParsoidOutputStash(),
92 $services->getParserOutputAccess(),
93 $services->getParsoidSiteConfig(),
94 $services->getHtmlTransformFactory(),
95 $services->getContentHandlerFactory(),
96 $services->getLanguageFactory(),
97 $services->getRedirectStore(),
98 $services->getLanguageConverterFactory(),
99 $services->getTitleFactory(),
100 $services->getConnectionProvider(),
101 $services->getChangeTagsStore(),
102 StatsFactory::newNull(),
103 $services->getShadowPageLoader(),
104 $services->getPageBundleParserOutputConverter(),
111 $parserOpts->disableContentConversion(
false );
112 $parserOpts->disableTitleConversion(
false );
117 private function getParserOutput(
127 $mwInstance = $this->getServiceContainer();
129 $languageFactory = $mwInstance->getLanguageFactory();
130 $parser = $mwInstance->getParser();
131 $parserOptions = $this->getParserOptions(
132 $languageFactory->getParentLanguage( $baseLanguage )
135 $content = $mwInstance->getRevisionLookup()
136 ->getRevisionByTitle( $pageTitle )
137 ->getContent( SlotRecord::MAIN );
138 $wikiContent = ( $content instanceof
TextContent ) ? $content->getText() :
'';
140 $po = $parser->parse( $wikiContent, $pageTitle, $parserOptions );
142 $pipeline = $mwInstance->getDefaultOutputPipeline();
143 $options = [
'deduplicateStyles' => false ];
144 return $pipeline->run( $po, $parserOptions, $options );
147 private function getParsoidOutput(
149 Bcp47Code $targetVariant,
153 $htmlOutputRendererHelper = $this->newPageRestHelperFactory()->newHtmlOutputRendererHelper( $pageTitle, [
156 ], $user,
null,
false, $parserOptions );
157 $htmlOutputRendererHelper->setVariantConversionLanguage( $targetVariant );
159 $po = $htmlOutputRendererHelper->getHtml();
160 $pipeline = $this->getServiceContainer()->getDefaultOutputPipeline();
161 $options = [
'deduplicateStyles' => false ];
162 return $pipeline->run( $po, $parserOptions, $options );
165 private function getWords(
string $output ): array {
166 $tagsRemoved = strip_tags( $output );
167 $words = preg_split(
'/\s+/', trim( $tagsRemoved ), -1, PREG_SPLIT_NO_EMPTY );
171 private function getBody(
string $output ): string {
172 $dom = new DOMDocument();
174 @$dom->loadHTML( $output );
175 $body = $dom->getElementsByTagName(
'body' )->item( 0 );
176 if ( $body ===
null ) {
181 return $body->textContent;
184 private function compareOutput(
187 string $converterUsed
189 $parsoidWords = $this->getWords( $this->getBody( $parsoidText ) );
190 $parserWords = $this->getWords( $parserText );
192 $parserWordCount = count( $parserWords );
193 $parsoidWordCount = count( $parsoidWords );
194 $this->output(
"Word count: Parsoid: $parsoidWordCount; Parser: $parserWordCount\n" );
196 $this->outputSimilarity( $parsoidWords, $parserWords );
197 $this->output(
"\n" );
198 $this->outputDiff( $parsoidWords, $parserWords, $converterUsed );
201 private function getConverterUsed(
ParserOutput $parsoidOutput ): string {
202 $isCoreConverterUsed = strpos(
203 $parsoidOutput->getContentHolderText(),
204 'Variant conversion performed using the core LanguageConverter'
207 if ( $isCoreConverterUsed ) {
208 return 'Core LanguageConverter';
210 return 'Parsoid LanguageConverter';
215 private function mb_sprintf(
string $format,
string ...$args ): string {
219 preg_replace_callback(
220 '/(?<=%|%-)\d+(?=s)/',
221 static function ( array $matches ) use ( &$params ) {
222 $value = array_shift( $params );
224 return (
string)( strlen( $value ) - mb_strlen( $value ) + $matches[0] );
232 private function outputSimilarity( array $parsoidWords, array $parserWords ): void {
233 $parsoidOutput = implode(
' ', $parsoidWords );
234 $parserOutput = implode(
' ', $parserWords );
236 'Total characters: Parsoid: ' . strlen( $parsoidOutput ) .
237 '; Parser: ' . strlen( $parserOutput ) .
"\n"
240 $similarityPercent = 0;
241 $similarCharacters = similar_text( $parsoidOutput, $parserOutput, $similarityPercent );
242 $similarityPercent = round( $similarityPercent, 2 );
245 "Similarity via similar_text(): $similarityPercent%; Similar characters: $similarCharacters"
249 private function outputDiff( array $parsoidWords, array $parserWords,
string $converterUsed ): void {
250 $out = str_repeat(
'-', 96 ) .
"\n";
251 $out .= sprintf(
"| %5s | %-35s | %-35s | %-8s |\n",
'Line',
'Parsoid',
'Parser',
'Diff' );
252 $out .= sprintf(
"| %5s | %-35s | %-35s | %-8s |\n",
'',
"($converterUsed)",
'',
'' );
253 $out .= str_repeat(
'-', 96 ) .
"\n";
256 $diff =
new Diff( $parsoidWords, $parserWords );
258 $this->output( $e->getMessage() );
259 $this->fatalError(
'Encountered ComplexityException while computing diff' );
264 foreach ( $wordDiffFormat as $index => $wordDiff ) {
265 $action = $wordDiff[
'action'];
266 $old = $wordDiff[
'old'] ??
null;
267 $new = $wordDiff[
'new'] ??
null;
269 $out .= $this->mb_sprintf(
270 "| %5s | %-35s | %-35s | %-8s |\n",
271 str_pad( (
string)( $index + 1 ), 5,
' ', STR_PAD_LEFT ),
272 mb_strimwidth( $old ??
'- N/A -', 0, 35,
'…' ),
273 mb_strimwidth( $new ??
'- N/A -', 0, 35,
'…' ),
279 $out .= str_repeat(
'-', 96 ) .
"\n";
280 $this->output(
"\n" . $out );