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