SourcePP
Several modern C++20 libraries for sanely parsing Valve's formats.
Loading...
Searching...
No Matches
KV1.h
Go to the documentation of this file.
1#pragma once
2
3#include <concepts>
4#include <string>
5#include <string_view>
6#include <vector>
7
8#include <BufferStream.h>
10#include <sourcepp/FS.h>
11#include <sourcepp/String.h>
12
13namespace kvpp {
14
15template<typename V>
16concept KV1ValueType = std::convertible_to<V, std::string_view>
17 || std::same_as<V, bool>
18 || std::same_as<V, int32_t>
19 || std::same_as<V, int64_t>
20 || std::same_as<V, float>;
21
22template<typename S, typename K>
23requires std::convertible_to<S, std::string_view>
25public:
27 [[nodiscard]] std::string_view getKey() const {
28 return this->key;
29 }
30
32 [[nodiscard]] std::string_view getValue() const {
33 return this->value;
34 }
35
37 template<KV1ValueType V>
38 [[nodiscard]] V getValue() const {
39 if constexpr (std::convertible_to<V, std::string_view>) {
40 return this->value;
41 } else if constexpr (std::same_as<V, bool>) {
42 return static_cast<bool>(this->getValue<int32_t>());
43 } else if constexpr (std::same_as<V, int32_t>) {
44 if (this->value.length() == 10 && this->value.starts_with("0x") && sourcepp::parser::text::isNumber(this->value.substr(2))) {
45 return std::stoi(std::string{this->value.substr(2)}, nullptr, 16);
46 }
47 return std::stoi(std::string{this->value});
48 } else if constexpr (std::same_as<V, int64_t>) {
49 if (this->value.length() == 18 && this->value.starts_with("0x") && sourcepp::parser::text::isNumber(this->value.substr(2))) {
50 return std::stoll(std::string{this->value.substr(2)}, nullptr, 16);
51 }
52 return std::stoll(std::string{this->value});
53 } else if constexpr (std::same_as<V, float>) {
54 return std::stof(std::string{this->value});
55 }
56 return V{};
57 }
58
60 [[nodiscard]] std::string_view getConditional() const {
61 return this->conditional;
62 }
63
65 [[nodiscard]] bool hasChild(std::string_view childKey) const {
66 return !this->operator[](childKey).isInvalid();
67 }
68
70 [[nodiscard]] uint64_t getChildCount() const {
71 return this->children.size();
72 }
73
75 [[nodiscard]] uint64_t getChildCount(std::string_view childKey) const {
76 uint64_t count = 0;
77 for (const KV1ElementBase& element : this->children) {
78 if (sourcepp::string::iequals(element.key, childKey)) {
79 ++count;
80 }
81 }
82 return count;
83 }
84
86 [[nodiscard]] const std::vector<K>& getChildren() const {
87 return this->children;
88 }
89
90 using iterator = typename std::vector<K>::iterator;
91
92 [[nodiscard]] constexpr iterator begin() {
93 return this->children.begin();
94 }
95
96 [[nodiscard]] constexpr iterator end() {
97 return this->children.end();
98 }
99
100 using const_iterator = typename std::vector<K>::const_iterator;
101
102 [[nodiscard]] constexpr const_iterator begin() const {
103 return this->children.begin();
104 }
105
106 [[nodiscard]] constexpr const_iterator end() const {
107 return this->children.end();
108 }
109
110 [[nodiscard]] constexpr const_iterator cbegin() const {
111 return this->children.cbegin();
112 }
113
114 [[nodiscard]] constexpr const_iterator cend() const {
115 return this->children.cend();
116 }
117
119 [[nodiscard]] const KV1ElementBase& operator[](unsigned int n) const {
120 return this->children.at(n);
121 }
122
124 [[nodiscard]] const KV1ElementBase& operator[](std::string_view childKey) const {
125 return this->operator()(childKey);
126 }
127
129 [[nodiscard]] const KV1ElementBase& operator()(std::string_view childKey) const {
130 for (const auto& element : this->children) {
131 if (sourcepp::string::iequals(element.getKey(), childKey)) {
132 return element;
133 }
134 }
135 return getInvalid();
136 }
137
139 [[nodiscard]] const KV1ElementBase& operator()(std::string_view childKey, unsigned int n) const {
140 unsigned int count = 0;
141 for (const auto& element : this->children) {
142 if (sourcepp::string::iequals(element.getKey(), childKey)) {
143 if (count == n) {
144 return element;
145 }
146 ++count;
147 }
148 }
149 return getInvalid();
150 }
151
153 [[nodiscard]] bool isInvalid() const {
154 return this == &getInvalid();
155 }
156
157 static const KV1ElementBase& getInvalid() {
158 static KV1ElementBase element;
159 return element;
160 }
161
162protected:
163 KV1ElementBase() = default;
164
165 static void read(BufferStreamReadOnly& stream, BufferStream& backing, std::vector<K>& elements, const sourcepp::parser::text::EscapeSequenceMap& escapeSequences) {
166 using namespace sourcepp;
167 while (true) {
168 // Check if the block is over
169 parser::text::eatWhitespaceAndSingleLineComments(stream);
170 if (stream.peek<char>() == '}') {
171 stream.skip();
172 break;
173 }
174 // Read key
175 {
176 auto childKey = parser::text::readStringToBuffer(stream, backing, parser::text::DEFAULT_STRING_START, parser::text::DEFAULT_STRING_END, escapeSequences);
177 elements.push_back(K{});
178 elements.back().key = childKey;
179 parser::text::eatWhitespaceAndSingleLineComments(stream);
180 }
181 // Read value
182 if (stream.peek<char>() != '{') {
183 elements.back().value = parser::text::readStringToBuffer(stream, backing, parser::text::DEFAULT_STRING_START, parser::text::DEFAULT_STRING_END, escapeSequences);
184 parser::text::eatWhitespaceAndSingleLineComments(stream);
185 }
186 // Read conditional
187 if (stream.peek<char>() == '[') {
188 elements.back().conditional = parser::text::readStringToBuffer(stream, backing, "[", "]", escapeSequences);
189 parser::text::eatWhitespaceAndSingleLineComments(stream);
190 }
191 // Read block
192 if (stream.peek<char>() == '{') {
193 stream.skip();
194 parser::text::eatWhitespaceAndSingleLineComments(stream);
195 if (stream.peek<char>() != '}') {
196 KV1ElementBase<S, K>::read(stream, backing, elements.back().children, escapeSequences);
197 } else {
198 stream.skip();
199 }
200 }
201 }
202 }
203
204 S key = ""; // NOLINT(*-redundant-string-init)
205 S value = ""; // NOLINT(*-redundant-string-init)
206 S conditional = ""; // NOLINT(*-redundant-string-init)
207 std::vector<K> children;
208};
209
210template<typename S = std::string_view>
211requires std::convertible_to<S, std::string_view>
212class KV1ElementReadable : public KV1ElementBase<S, KV1ElementReadable<S>> {
213 friend class KV1ElementBase<S, KV1ElementReadable<S>>;
214
215protected:
217};
218
219template<typename S = std::string_view>
220requires std::convertible_to<S, std::string_view>
221class KV1 : public KV1ElementReadable<S> {
222public:
223 explicit KV1(std::string_view kv1Data, bool useEscapeSequences_ = false)
224 : KV1ElementReadable<S>()
225 , useEscapeSequences(useEscapeSequences_) {
226 if (kv1Data.empty()) {
227 return;
228 }
229 BufferStreamReadOnly stream{kv1Data.data(), kv1Data.size()};
230
231 // Multiply by 2 to ensure buffer will have enough space (very generous)
232 this->backingData.resize(kv1Data.size() * 2);
233 BufferStream backing{this->backingData, false};
234 try {
236 } catch (const std::overflow_error&) {}
237 }
238
239protected:
243
244 std::string backingData;
246};
247
248template<typename S = std::string>
249requires std::convertible_to<S, std::string_view>
250class KV1ElementWritable : public KV1ElementBase<S, KV1ElementWritable<S>> {
251 friend class KV1ElementBase<S, KV1ElementWritable<S>>;
252
253public:
255 void setKey(std::string_view key_) {
256 this->key = key_;
257 }
258
260 template<KV1ValueType V>
261 void setValue(V value_) {
262 if constexpr (std::convertible_to<V, std::string_view>) {
263 this->value = std::string_view{value_};
264 } else {
265 this->setValue(std::to_string(value_));
266 }
267 }
268
270 template<KV1ValueType V>
272 this->setValue(value_);
273 return *this;
274 }
275
277 void setConditional(std::string_view conditional_) {
278 this->conditional = conditional_;
279 }
280
281 template<KV1ValueType V = std::string_view>
282 KV1ElementWritable& addChild(std::string_view key_, V value_ = {}, std::string_view conditional_ = "") {
284 elem.setKey(key_);
285 elem.setValue(value_);
286 elem.setConditional(conditional_);
287 this->children.push_back(elem);
288 return this->children.back();
289 }
290
292 [[nodiscard]] KV1ElementWritable& operator[](unsigned int n) {
293 return this->children.at(n);
294 }
295
297 [[nodiscard]] KV1ElementWritable& operator[](std::string_view childKey) {
298 return this->operator()(childKey);
299 }
300
302 [[nodiscard]] KV1ElementWritable& operator()(std::string_view childKey) {
303 for (auto& element : this->children) {
304 if (sourcepp::string::iequals(element.getKey(), childKey)) {
305 return element;
306 }
307 }
308 return this->addChild(childKey);
309 }
310
312 [[nodiscard]] KV1ElementWritable& operator()(std::string_view childKey, unsigned int n) {
313 unsigned int count = 0;
314 for (auto& element: this->children) {
315 if (sourcepp::string::iequals(element.getKey(), childKey)) {
316 if (count == n) {
317 return element;
318 }
319 ++count;
320 }
321 }
322 return this->addChild(childKey);
323 }
324
326 void removeChild(unsigned int n) {
327 if (this->children.size() > n) {
328 this->children.erase(this->children.begin() + n);
329 }
330 }
331
333 void removeChild(std::string_view childKey, int n = -1) {
334 unsigned int count = 0;
335 for (auto element = this->children.begin(); element != this->children.end(); ++element) {
336 if (sourcepp::string::iequals(element->getKey(), childKey)) {
337 if (n < 0 || count == n) {
338 element = this->children.erase(element);
339 if (count == n) {
340 return;
341 }
342 }
343 ++count;
344 }
345 }
346 }
347
348protected:
350
351 static void write(BufferStream& stream, const std::vector<KV1ElementWritable>& elements, unsigned short indentLevel, const sourcepp::parser::text::EscapeSequenceMap& escapeSequences) {
352 using namespace sourcepp;
353 constexpr auto writeIndentation = [](BufferStream& stream_, unsigned short indentLevel_) {
354 for (unsigned short i = 0; i < indentLevel_; i++) {
355 stream_.write('\t');
356 }
357 };
358 constexpr auto writeQuotedString = [](BufferStream& stream_, std::string_view str, const parser::text::EscapeSequenceMap& escapeSequences_, char quoteStart = '\"', char quoteEnd = '\"') {
359 stream_.write(quoteStart);
360 if (!str.empty()) {
361 stream_.write(parser::text::convertSpecialCharsToEscapes(str, escapeSequences_), false);
362 }
363 stream_.write(quoteEnd);
364 };
365
366 for (auto& elem : elements) {
367 writeIndentation(stream, indentLevel);
368 writeQuotedString(stream, elem.key, escapeSequences);
369 if (!elem.value.empty() || elem.children.empty()) {
370 stream.write(' ');
371 writeQuotedString(stream, elem.value, escapeSequences);
372 }
373 if (!elem.conditional.empty()) {
374 stream.write(' ');
375 writeQuotedString(stream, elem.conditional, escapeSequences, '[', ']');
376 }
377 stream.write('\n');
378 if (!elem.children.empty()) {
379 writeIndentation(stream, indentLevel);
380 stream << '{' << '\n';
381 write(stream, elem.children, indentLevel + 1, escapeSequences);
382 writeIndentation(stream, indentLevel);
383 stream << '}' << '\n';
384 }
385 }
386 }
387};
388
389template<typename S = std::string>
390requires std::convertible_to<S, std::string_view>
391class KV1Writer : public KV1ElementWritable<S> {
392public:
393 explicit KV1Writer(std::string_view kv1Data = "", bool useEscapeSequences_ = false)
394 : KV1ElementWritable<S>()
395 , useEscapeSequences(useEscapeSequences_) {
396 if (kv1Data.empty()) {
397 return;
398 }
399 BufferStreamReadOnly stream{kv1Data.data(), kv1Data.size()};
400
401 // Multiply by 2 to ensure buffer will have enough space (very generous)
402 // Also it's ok that this backing data dies, it will perform copies here
403 std::string backingData(kv1Data.size() * 2, '\0');
404 BufferStream backing{backingData, false};
405 try {
407 } catch (const std::overflow_error&) {}
408 }
409
410 [[nodiscard]] std::string bake() const {
411 std::string buffer;
412 BufferStream stream{buffer};
414 buffer.resize(stream.size());
415 return buffer;
416 }
417
418 void bake(const std::string& kv1Path) const {
419 sourcepp::fs::writeFileText(kv1Path, this->bake());
420 }
421
422protected:
423 using KV1ElementWritable<S>::getKey;
427 using KV1ElementWritable<S>::operator=;
430
432};
433
434} // namespace kvpp
std::string_view getKey() const
Get the key associated with the element.
Definition: KV1.h:27
uint64_t getChildCount() const
Get the number of child elements.
Definition: KV1.h:70
static const KV1ElementBase & getInvalid()
Definition: KV1.h:157
typename std::vector< K >::iterator iterator
Definition: KV1.h:90
static void read(BufferStreamReadOnly &stream, BufferStream &backing, std::vector< K > &elements, const sourcepp::parser::text::EscapeSequenceMap &escapeSequences)
Definition: KV1.h:165
constexpr const_iterator end() const
Definition: KV1.h:106
constexpr const_iterator begin() const
Definition: KV1.h:102
std::string_view getConditional() const
Get the conditional associated with the element.
Definition: KV1.h:60
V getValue() const
Get the value associated with the element as the given type.
Definition: KV1.h:38
constexpr const_iterator cend() const
Definition: KV1.h:114
bool isInvalid() const
Check if the given element is invalid.
Definition: KV1.h:153
constexpr iterator begin()
Definition: KV1.h:92
KV1ElementBase()=default
const KV1ElementBase & operator[](std::string_view childKey) const
Get the first child element of the element with the given key.
Definition: KV1.h:124
constexpr const_iterator cbegin() const
Definition: KV1.h:110
std::string_view getValue() const
Get the value associated with the element.
Definition: KV1.h:32
const std::vector< K > & getChildren() const
Get the child elements of the element.
Definition: KV1.h:86
uint64_t getChildCount(std::string_view childKey) const
Get the number of child elements with the given key.
Definition: KV1.h:75
const KV1ElementBase & operator()(std::string_view childKey) const
Get the first child element of the element with the given key.
Definition: KV1.h:129
const KV1ElementBase & operator[](unsigned int n) const
Get the child element of the element at the given index.
Definition: KV1.h:119
typename std::vector< K >::const_iterator const_iterator
Definition: KV1.h:100
const KV1ElementBase & operator()(std::string_view childKey, unsigned int n) const
Get the nth child element of the element with the given key.
Definition: KV1.h:139
std::vector< K > children
Definition: KV1.h:207
constexpr iterator end()
Definition: KV1.h:96
bool hasChild(std::string_view childKey) const
Check if the element has one or more children with the given name.
Definition: KV1.h:65
void setKey(std::string_view key_)
Set the key associated with the element.
Definition: KV1.h:255
void removeChild(std::string_view childKey, int n=-1)
Remove a child element from the element. -1 means all children with the given key.
Definition: KV1.h:333
void removeChild(unsigned int n)
Remove a child element from the element.
Definition: KV1.h:326
static void write(BufferStream &stream, const std::vector< KV1ElementWritable > &elements, unsigned short indentLevel, const sourcepp::parser::text::EscapeSequenceMap &escapeSequences)
Definition: KV1.h:351
KV1ElementWritable & operator=(V value_)
Set the value associated with the element.
Definition: KV1.h:271
void setConditional(std::string_view conditional_)
Set the conditional associated with the element.
Definition: KV1.h:277
KV1ElementWritable & operator()(std::string_view childKey, unsigned int n)
Get the nth child element of the element with the given key, or create a new element if it doesn't ex...
Definition: KV1.h:312
KV1ElementWritable & addChild(std::string_view key_, V value_={}, std::string_view conditional_="")
Definition: KV1.h:282
void setValue(V value_)
Set the value associated with the element.
Definition: KV1.h:261
KV1ElementWritable & operator[](unsigned int n)
Get the child element of the element at the given index.
Definition: KV1.h:292
KV1ElementWritable & operator()(std::string_view childKey)
Get the first child element of the element with the given key, or create a new element if it doesn't ...
Definition: KV1.h:302
KV1ElementWritable & operator[](std::string_view childKey)
Get the first child element of the element with the given key, or create a new element if it doesn't ...
Definition: KV1.h:297
void bake(const std::string &kv1Path) const
Definition: KV1.h:418
std::string bake() const
Definition: KV1.h:410
KV1Writer(std::string_view kv1Data="", bool useEscapeSequences_=false)
Definition: KV1.h:393
bool useEscapeSequences
Definition: KV1.h:431
Definition: KV1.h:221
bool useEscapeSequences
Definition: KV1.h:245
KV1(std::string_view kv1Data, bool useEscapeSequences_=false)
Definition: KV1.h:223
std::string backingData
Definition: KV1.h:244
Definition: KV1.h:13
bool writeFileText(const std::string &filepath, const std::string &text)
Definition: FS.cpp:36
std::unordered_map< char, char > EscapeSequenceMap
Definition: Text.h:17
const EscapeSequenceMap & getDefaultEscapeSequencesOrNone(bool useEscapes)
Definition: Text.cpp:27
bool isNumber(char c)
If a char is a numerical character (0-9).
Definition: Text.cpp:48
bool iequals(std::string_view s1, std::string_view s2)
Definition: String.cpp:61
Definition: LZMA.h:11