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