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
2declare( strict_types = 1 );
3
4namespace Wikimedia\Parsoid\Tokens;
5
6use Wikimedia\JsonCodec\Hint;
7use Wikimedia\JsonCodec\JsonCodecable;
8use Wikimedia\JsonCodec\JsonCodecableTrait;
9use Wikimedia\Parsoid\Utils\Utils;
10
11/**
12 * Represents a Key-value pair.
13 */
14class 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}