Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 43 |
|
0.00% |
0 / 10 |
CRAP | |
0.00% |
0 / 1 |
KV | |
0.00% |
0 / 43 |
|
0.00% |
0 / 10 |
650 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
__clone | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
30 | |||
lookupKV | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
30 | |||
lookup | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
keyOffset | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
valueOffset | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
jsonSerialize | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
20 | |||
toJsonArray | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
newFromJsonArray | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 | |||
jsonClassHintFor | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
30 |
1 | <?php |
2 | declare( strict_types = 1 ); |
3 | |
4 | namespace Wikimedia\Parsoid\Tokens; |
5 | |
6 | use Wikimedia\JsonCodec\Hint; |
7 | use Wikimedia\JsonCodec\JsonCodecable; |
8 | use Wikimedia\JsonCodec\JsonCodecableTrait; |
9 | use Wikimedia\Parsoid\Utils\Utils; |
10 | |
11 | /** |
12 | * Represents a Key-value pair. |
13 | */ |
14 | class KV implements JsonCodecable, \JsonSerializable { |
15 | use JsonCodecableTrait; |
16 | |
17 | /** |
18 | * Commonly a string, but where the key might be templated, |
19 | * this can be an array of tokens even. |
20 | * |
21 | * @var string|Token|array<Token|string> |
22 | */ |
23 | public $k; |
24 | |
25 | /** @var string|Token|array<Token|string>|KV[] */ |
26 | public $v; |
27 | |
28 | /** Wikitext source offsets */ |
29 | public ?KVSourceRange $srcOffsets; |
30 | |
31 | /** Wikitext source */ |
32 | public ?string $ksrc; |
33 | |
34 | /** Wikitext source */ |
35 | public ?string $vsrc; |
36 | |
37 | /** |
38 | * @param string|Token|array<Token|string> $k |
39 | * Commonly a string, but where the key might be templated, |
40 | * this can be an array of tokens even. |
41 | * @param string|Token|array<Token|string>|KV[] $v |
42 | * The value: string, token, of an array of tokens |
43 | * @param ?KVSourceRange $srcOffsets wikitext source offsets |
44 | * @param ?string $ksrc |
45 | * @param ?string $vsrc |
46 | */ |
47 | public function __construct( |
48 | $k, $v, ?KVSourceRange $srcOffsets = null, ?string $ksrc = null, |
49 | ?string $vsrc = null |
50 | ) { |
51 | $this->k = $k; |
52 | $this->v = $v; |
53 | $this->srcOffsets = $srcOffsets; |
54 | $this->ksrc = $ksrc; |
55 | $this->vsrc = $vsrc; |
56 | } |
57 | |
58 | public function __clone() { |
59 | // Deep clone non-primitive properties |
60 | foreach ( [ 'k', 'v' ] as $f ) { |
61 | if ( is_array( $this->$f ) ) { |
62 | $this->$f = Utils::cloneArray( $this->$f ); |
63 | } elseif ( is_object( $this->$f ) ) { |
64 | $this->$f = clone $this->$f; |
65 | } |
66 | } |
67 | if ( $this->srcOffsets !== null ) { |
68 | $this->srcOffsets = clone $this->srcOffsets; |
69 | } |
70 | } |
71 | |
72 | /** |
73 | * BUG: When there are multiple matching attributes, Sanitizer lets the last one win |
74 | * whereas this method is letting the first one win. This can introduce subtle bugs! |
75 | * |
76 | * Lookup a string key in a KV array and return the first matching KV object |
77 | * |
78 | * @param KV[]|null $kvs |
79 | * @param string $key |
80 | * @return ?KV |
81 | */ |
82 | public static function lookupKV( ?array $kvs, string $key ): ?KV { |
83 | if ( $kvs === null ) { |
84 | return null; |
85 | } |
86 | |
87 | foreach ( $kvs as $kv ) { |
88 | // PORT-FIXME: JS trim() will remove non-ASCII spaces (such as NBSP) too, |
89 | // while PHP's won't. Does that matter? |
90 | if ( is_string( $kv->k ) && trim( $kv->k ) === $key ) { |
91 | return $kv; |
92 | } |
93 | } |
94 | |
95 | return null; |
96 | } |
97 | |
98 | /** |
99 | * Lookup a string key (first occurrence) in a KV array |
100 | * and return the value of the KV object |
101 | * |
102 | * @param KV[]|null $kvs |
103 | * @param string $key |
104 | * @return string|Token|array<Token|string>|null |
105 | */ |
106 | public static function lookup( ?array $kvs, string $key ) { |
107 | $kv = self::lookupKV( $kvs, $key ); |
108 | // PORT_FIXME: Potential bug lurking here ... if $kv->v is an array |
109 | // this will return a copy, which if modified will not reflect |
110 | // in the original KV object. |
111 | return $kv->v ?? null; |
112 | } |
113 | |
114 | /** |
115 | * Return the key portion of the KV's source offsets, or else null |
116 | * if no source offsets are known. |
117 | * @return SourceRange|null |
118 | */ |
119 | public function keyOffset(): ?SourceRange { |
120 | // @phan-suppress-next-line PhanCoalescingNeverNull $this->srcOffsets is nullable |
121 | return $this->srcOffsets->key ?? null; |
122 | } |
123 | |
124 | /** |
125 | * Return the value portion of the KV's source offsets, or else null |
126 | * if no source offsets are known. |
127 | * @return SourceRange|null |
128 | */ |
129 | public function valueOffset(): ?SourceRange { |
130 | // @phan-suppress-next-line PhanCoalescingNeverNull $this->srcOffsets is nullable |
131 | return $this->srcOffsets->value ?? null; |
132 | } |
133 | |
134 | /** |
135 | * @inheritDoc |
136 | */ |
137 | public function jsonSerialize(): array { |
138 | $ret = [ "k" => $this->k, "v" => $this->v ]; |
139 | if ( $this->srcOffsets ) { |
140 | $ret["srcOffsets"] = $this->srcOffsets; |
141 | } |
142 | if ( isset( $this->ksrc ) ) { |
143 | $ret["ksrc"] = $this->ksrc; |
144 | } |
145 | if ( isset( $this->vsrc ) ) { |
146 | $ret["vsrc"] = $this->vsrc; |
147 | } |
148 | return $ret; |
149 | } |
150 | |
151 | /** @inheritDoc */ |
152 | public function toJsonArray(): array { |
153 | return $this->jsonSerialize(); |
154 | } |
155 | |
156 | /** @inheritDoc */ |
157 | public static function newFromJsonArray( array $json ) { |
158 | return new self( |
159 | $json['k'], $json['v'], |
160 | $json['srcOffsets'] ?? null, |
161 | $json['ksrc'] ?? null, |
162 | $json['vsrc'] ?? null |
163 | ); |
164 | } |
165 | |
166 | /** @inheritDoc */ |
167 | public static function jsonClassHintFor( string $keyName ) { |
168 | switch ( $keyName ) { |
169 | case 'k': |
170 | case 'v': |
171 | // Hint these as "array of Token" which is the most common |
172 | // thing after "string". |
173 | return Hint::build( Token::class, Hint::INHERITED, Hint::LIST ); |
174 | case 'srcOffsets': |
175 | return Hint::build( KVSourceRange::class, Hint::USE_SQUARE ); |
176 | default: |
177 | return null; |
178 | } |
179 | } |
180 | } |