Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
6.25% covered (danger)
6.25%
5 / 80
10.00% covered (danger)
10.00%
2 / 20
CRAP
0.00% covered (danger)
0.00%
0 / 1
MathIdGenerator
6.25% covered (danger)
6.25%
5 / 80
10.00% covered (danger)
10.00%
2 / 20
1426.10
0.00% covered (danger)
0.00%
0 / 1
 newFromRevisionRecord
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
12
 getKeys
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 __construct
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 newFromRevisionId
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 newFromTitle
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getIdList
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 formatIds
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 parserKey2fId
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
20
 getInputHash
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getIdsFromContent
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 getContentIdMap
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
20
 guessIdFromContent
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
20
 getMathTags
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getWikiText
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getRevisionId
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setUseCustomIds
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getTagFromId
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
30
 getUniqueFromId
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
30
 formatKey
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getUserInputTex
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3use MediaWiki\Extension\Math\MathSource;
4use MediaWiki\MediaWikiServices;
5use MediaWiki\Revision\RevisionRecord;
6use MediaWiki\Revision\SlotRecord;
7
8class MathIdGenerator {
9
10    public const CONTENT_POS = 1;
11    public const ATTRIB_POS = 2;
12
13    private string $wikiText;
14    private array $mathTags;
15    private int $revisionId;
16    /** @var int[] */
17    private $contentAccessStats = [];
18    private string $format = "math.%d.%d";
19    private bool $useCustomIds = false;
20    /** @var int[]|null */
21    private $keys;
22    /** @var string[][]|null */
23    private $contentIdMap;
24
25    public static function newFromRevisionRecord( RevisionRecord $revisionRecord ): MathIdGenerator {
26        $contentModel = $revisionRecord
27            ->getSlot( SlotRecord::MAIN, RevisionRecord::RAW )
28            ->getModel();
29        if ( $contentModel !== CONTENT_MODEL_WIKITEXT ) {
30            throw new RuntimeException( "MathIdGenerator supports only CONTENT_MODEL_WIKITEXT" );
31        }
32        $content = $revisionRecord->getContent( SlotRecord::MAIN );
33        if ( !$content instanceof TextContent ) {
34            throw new RuntimeException( "MathIdGenerator supports only TextContent" );
35        }
36        return new self(
37            $content->getText(),
38            $revisionRecord->getId()
39        );
40    }
41
42    /**
43     * @return int[] Array mapping key names to their position
44     */
45    public function getKeys() {
46        if ( !isset( $this->keys ) ) {
47            $this->keys = array_flip( array_keys( $this->mathTags ) );
48        }
49        return $this->keys;
50    }
51
52    public function __construct( string $wikiText, int $revisionId = 0 ) {
53        $wikiText = Sanitizer::removeHTMLcomments( $wikiText );
54        $this->wikiText =
55            Parser::extractTagsAndParams( [ 'nowki', 'syntaxhighlight', 'math' ], $wikiText,
56                $tags );
57        $this->mathTags = array_filter( $tags, static function ( $v ) {
58            return $v[0] === 'math';
59        } );
60        $this->revisionId = $revisionId;
61    }
62
63    public static function newFromRevisionId( int $revId ): MathIdGenerator {
64        $revisionRecord = MediaWikiServices::getInstance()
65            ->getRevisionLookup()
66            ->getRevisionById( $revId );
67
68        return self::newFromRevisionRecord( $revisionRecord );
69    }
70
71    public static function newFromTitle( Title $title ): MathIdGenerator {
72        return self::newFromRevisionId( $title->getLatestRevID() );
73    }
74
75    public function getIdList() {
76        return $this->formatIds( $this->mathTags );
77    }
78
79    /**
80     * @param array $mathTags
81     *
82     * @return string[]
83     */
84    public function formatIds( $mathTags ) {
85        return array_map( [ $this, 'parserKey2fId' ], array_keys( $mathTags ) );
86    }
87
88    /**
89     * @param string $key
90     *
91     * @return string|null
92     */
93    public function parserKey2fId( $key ) {
94        if ( $this->useCustomIds ) {
95            if ( isset( $this->mathTags[$key][self::ATTRIB_POS]['id'] ) ) {
96                return $this->mathTags[$key][self::ATTRIB_POS]['id'];
97            }
98        }
99        if ( isset( $this->mathTags[$key] ) ) {
100            return $this->formatKey( $key );
101        }
102    }
103
104    public function getInputHash( $inputTex ) {
105        return pack( "H32", md5( $inputTex ) );
106    }
107
108    /**
109     * @param string $content
110     *
111     * @return string[]
112     */
113    public function getIdsFromContent( $content ) {
114        $contentIdMap = $this->getContentIdMap();
115        if ( array_key_exists( $content, $contentIdMap ) ) {
116            return $contentIdMap[$content];
117        }
118        return [];
119    }
120
121    /**
122     * @return string[][]
123     */
124    public function getContentIdMap() {
125        if ( !$this->contentIdMap ) {
126            $this->contentIdMap = [];
127            foreach ( $this->mathTags as $key => $tag ) {
128                $userInputTex = $this->getUserInputTex( $tag );
129                if ( !array_key_exists( $userInputTex,
130                        $this->contentIdMap )
131                ) {
132                    $this->contentIdMap[$userInputTex] = [];
133                }
134                $this->contentIdMap[$userInputTex][] =
135                    $this->parserKey2fId( $key );
136            }
137        }
138        return $this->contentIdMap;
139    }
140
141    public function guessIdFromContent( string $content ): ?string {
142        $allIds = $this->getIdsFromContent( $content );
143        $size = count( $allIds );
144        if ( $size == 0 ) {
145            return null;
146        }
147        if ( $size == 1 ) {
148            return $allIds[0];
149        }
150        if ( array_key_exists( $content, $this->contentAccessStats ) ) {
151            $this->contentAccessStats[$content]++;
152        } else {
153            $this->contentAccessStats[$content] = 0;
154        }
155        $currentIndex = $this->contentAccessStats[$content] % $size;
156        return $allIds[$currentIndex];
157    }
158
159    public function getMathTags(): array {
160        return $this->mathTags;
161    }
162
163    public function getWikiText(): string {
164        return $this->wikiText;
165    }
166
167    public function getRevisionId(): int {
168        return $this->revisionId;
169    }
170
171    public function setUseCustomIds( bool $useCustomIds ): void {
172        $this->useCustomIds = $useCustomIds;
173    }
174
175    public function getTagFromId( string $eid ): ?array {
176        foreach ( $this->mathTags as $key => $mathTag ) {
177            if ( $eid == $this->formatKey( $key ) ) {
178                return $mathTag;
179            }
180            if ( isset( $mathTag[self::ATTRIB_POS]['id'] ) && $eid == $mathTag[self::ATTRIB_POS]['id'] ) {
181                return $mathTag;
182            }
183        }
184        return null;
185    }
186
187    public function getUniqueFromId( string $eid ): ?string {
188        foreach ( $this->mathTags as $key => $mathTag ) {
189            if ( $eid == $this->formatKey( $key ) ) {
190                return $key;
191            }
192            if ( isset( $mathTag[self::ATTRIB_POS]['id'] ) && $eid == $mathTag[self::ATTRIB_POS]['id'] ) {
193                return $key;
194            }
195        }
196        return null;
197    }
198
199    private function formatKey( string $key ): string {
200        $keys = $this->getKeys();
201        return sprintf( $this->format, $this->revisionId, $keys[$key] );
202    }
203
204    public function getUserInputTex( array $tag ): string {
205        return ( new MathSource( $tag[self::CONTENT_POS], $tag[self::ATTRIB_POS] ) )->getUserInputTex();
206    }
207}