Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
6.25% |
5 / 80 |
|
10.00% |
2 / 20 |
CRAP | |
0.00% |
0 / 1 |
MathIdGenerator | |
6.25% |
5 / 80 |
|
10.00% |
2 / 20 |
1426.10 | |
0.00% |
0 / 1 |
newFromRevisionRecord | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
12 | |||
getKeys | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
__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 / 10 |
|
0.00% |
0 / 1 |
20 | |||
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\Revision\RevisionRecord; |
6 | use MediaWiki\Revision\SlotRecord; |
7 | |
8 | class 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 | } |