Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
91.11% covered (success)
91.11%
41 / 45
88.89% covered (warning)
88.89%
8 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
ParserOutput
91.11% covered (success)
91.11%
41 / 45
88.89% covered (warning)
88.89%
8 / 9
16.18
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 sourcePageTemplate
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 translationPageTemplate
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 units
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 sourcePageTextForRendering
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPageTextForRendering
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
2
 sourcePageTextForSaving
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 sourcePageTemplateForDiffs
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 assertContainsOnlyInstancesOf
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2declare( strict_types = 1 );
3
4namespace MediaWiki\Extension\Translate\PageTranslation;
5
6use InvalidArgumentException;
7use Language;
8use MediaWiki\Extension\Translate\MessageLoading\Message;
9use Parser;
10
11/**
12 * Represents a parsing output produced by TranslatablePageParser.
13 *
14 * It is required generate translatable and translation page sources or just get the list of
15 * translations units.
16 *
17 * @author Niklas Laxström
18 * @license GPL-2.0-or-later
19 * @since 2020.08
20 */
21class ParserOutput {
22    /** @var string */
23    private $template;
24    /** @var Section[] */
25    private $sectionMap;
26    /** @var TranslationUnit[] */
27    private $unitMap;
28
29    public function __construct( string $template, array $sectionMap, array $unitMap ) {
30        $this->assertContainsOnlyInstancesOf( Section::class, '$sectionMap', $sectionMap );
31        $this->assertContainsOnlyInstancesOf( TranslationUnit::class, '$unitMap', $unitMap );
32
33        $this->template = $template;
34        $this->sectionMap = $sectionMap;
35        $this->unitMap = $unitMap;
36    }
37
38    /** Returns template that contains <translate> tags */
39    public function sourcePageTemplate(): string {
40        $replacements = [];
41        foreach ( $this->sectionMap as $ph => $section ) {
42            $replacements[$ph] = $section->wrappedContents();
43        }
44
45        return strtr( $this->template, $replacements );
46    }
47
48    /** Returns template that does not contain <translate> tags */
49    public function translationPageTemplate(): string {
50        $replacements = [];
51        foreach ( $this->sectionMap as $ph => $section ) {
52            $replacements[$ph] = $section->contents();
53        }
54
55        return strtr( $this->template, $replacements );
56    }
57
58    /** @return TranslationUnit[] */
59    public function units(): array {
60        return $this->unitMap;
61    }
62
63    /** Returns the source page wikitext used for rendering the page. */
64    public function sourcePageTextForRendering( Language $sourceLanguage ): string {
65        return $this->getPageTextForRendering( $sourceLanguage, $sourceLanguage, false );
66    }
67
68    /**
69     * @param Language $sourceLanguage Language of the translatable page
70     * @param Language $targetLanguage Language of the translation page; same as
71     *  $sourceLanguage when rendering the translatable page
72     * @param bool $wrapUntranslated Whether to wrap untranslated units in `<span>` or `<div>`
73     *  with appropriate language and directionality set
74     * @param array<string,Message> $messages Translations by translation unit;
75     *  empty when rendering the translatable page
76     * @param Parser|null $parser Wikitext parser to use when generating anchors for translated
77     *  headings; if `null`, no anchors will be generated
78     */
79    public function getPageTextForRendering(
80        Language $sourceLanguage,
81        Language $targetLanguage,
82        bool $wrapUntranslated,
83        array $messages = [],
84        ?Parser $parser = null
85    ): string {
86        $text = $this->translationPageTemplate();
87
88        foreach ( $this->unitMap as $ph => $s ) {
89            $t = $s->getTextForRendering(
90                $messages[$s->id] ?? null,
91                $sourceLanguage,
92                $targetLanguage,
93                $wrapUntranslated,
94                $parser
95            );
96            $text = str_replace( $ph, $t, $text );
97        }
98
99        // Replace {{TRANSLATIONLANGUAGE}} usage outside of translation units (T224810)
100        $text = preg_replace(
101            TranslationUnit::TRANSLATIONLANGUAGE_REGEX,
102            $targetLanguage->getCode(),
103            $text
104        );
105
106        return $text;
107    }
108
109    /** Returns the source page with translation unit markers. */
110    public function sourcePageTextForSaving(): string {
111        $text = $this->sourcePageTemplate();
112
113        foreach ( $this->unitMap as $ph => $s ) {
114            $text = str_replace( $ph, $s->getMarkedText(), $text );
115        }
116
117        return $text;
118    }
119
120    /** Returns the page text with translation tags and unit placeholders for easy diffs */
121    public function sourcePageTemplateForDiffs(): string {
122        $text = $this->sourcePageTemplate();
123
124        foreach ( $this->unitMap as $ph => $s ) {
125            $text = str_replace( $ph, "<!--T:{$s->id}-->", $text );
126        }
127
128        return $text;
129    }
130
131    private function assertContainsOnlyInstancesOf(
132        string $expected,
133        string $name,
134        array $x
135    ): void {
136        foreach ( $x as $item ) {
137            if ( !$item instanceof $expected ) {
138                $actual = gettype( $item );
139                throw new InvalidArgumentException(
140                    "Parameter $name must only contain instances of class $expected. Got $actual."
141                );
142            }
143        }
144    }
145}