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
91 [[nodiscard]] const KV1ElementBase& operator[](unsigned int n) const {
92 return this->children.at(n);
93 }
94
96 [[nodiscard]] const KV1ElementBase& operator[](std::string_view childKey) const {
97 return this->operator()(childKey);
98 }
99
101 [[nodiscard]] const KV1ElementBase& operator()(std::string_view childKey) const {
102 for (const auto& element : this->children) {
103 if (sourcepp::string::iequals(element.getKey(), childKey)) {
104 return element;
105 }
106 }
107 return getInvalid();
108 }
109
111 [[nodiscard]] const KV1ElementBase& operator()(std::string_view childKey, unsigned int n) const {
112 unsigned int count = 0;
113 for (const auto& element : this->children) {
114 if (sourcepp::string::iequals(element.getKey(), childKey)) {
115 if (count == n) {
116 return element;
117 }
118 ++count;
119 }
120 }
121 return getInvalid();
122 }
123
125 [[nodiscard]] bool isInvalid() const {
126 return this == &getInvalid();
127 }
128
129protected:
130 KV1ElementBase() = default;
131
132 static const KV1ElementBase& getInvalid() {
133 static KV1ElementBase element;
134 return element;
135 }
136
137 static void read(BufferStreamReadOnly& stream, BufferStream& backing, std::vector<K>& elements, const sourcepp::parser::text::EscapeSequenceMap& escapeSequences) {
138 using namespace sourcepp;
139 while (true) {
140 // Check if the block is over
141 parser::text::eatWhitespaceAndSingleLineComments(stream);
142 if (stream.peek<char>() == '}') {
143 stream.skip();
144 break;
145 }
146 // Read key
147 {
148 auto childKey = parser::text::readStringToBuffer(stream, backing, parser::text::DEFAULT_STRING_START, parser::text::DEFAULT_STRING_END, escapeSequences);
149 elements.push_back(K{});
150 elements.back().key = childKey;
151 parser::text::eatWhitespaceAndSingleLineComments(stream);
152 }
153 // Read value
154 if (stream.peek<char>() != '{') {
155 elements.back().value = parser::text::readStringToBuffer(stream, backing, parser::text::DEFAULT_STRING_START, parser::text::DEFAULT_STRING_END, escapeSequences);
156 parser::text::eatWhitespaceAndSingleLineComments(stream);
157 }
158 // Read conditional
159 if (stream.peek<char>() == '[') {
160 elements.back().conditional = parser::text::readStringToBuffer(stream, backing, "[", "]", escapeSequences);
161 parser::text::eatWhitespaceAndSingleLineComments(stream);
162 }
163 // Read block
164 if (stream.peek<char>() == '{') {
165 stream.skip();
166 parser::text::eatWhitespaceAndSingleLineComments(stream);
167 if (stream.peek<char>() != '}') {
168 KV1ElementBase<S, K>::read(stream, backing, elements.back().children, escapeSequences);
169 } else {
170 stream.skip();
171 }
172 }
173 }
174 }
175
176 S key = ""; // NOLINT(*-redundant-string-init)
177 S value = ""; // NOLINT(*-redundant-string-init)
178 S conditional = ""; // NOLINT(*-redundant-string-init)
179 std::vector<K> children;
180};
181
182class KV1ElementReadable : public KV1ElementBase<std::string_view, KV1ElementReadable> {
183 friend class KV1ElementBase<std::string_view, KV1ElementReadable>;
184
185protected:
187};
188
189class KV1 : public KV1ElementReadable {
190public:
191 explicit KV1(std::string_view kv1Data, bool useEscapeSequences_ = false);
192
193protected:
197
198 std::string backingData;
199 bool useEscapeSequences;
200};
201
202template<typename S = std::string>
203requires std::convertible_to<S, std::string_view>
204class KV1ElementWritable : public KV1ElementBase<S, KV1ElementWritable<S>> {
205 friend class KV1ElementBase<std::string, KV1ElementWritable<S>>;
206
207public:
209 void setKey(std::string_view key_) {
210 this->key = key_;
211 }
212
214 template<KV1ValueType V>
215 void setValue(V value_) {
216 if constexpr (std::convertible_to<V, std::string_view>) {
217 this->value = std::string_view{value_};
218 } else {
219 this->setValue(std::to_string(value_));
220 }
221 }
222
224 template<KV1ValueType V>
226 this->setValue(value_);
227 return *this;
228 }
229
231 void setConditional(std::string_view conditional_) {
232 this->conditional = conditional_;
233 }
234
235 template<KV1ValueType V = std::string_view>
236 KV1ElementWritable& addChild(std::string_view key_, V value_ = {}, std::string_view conditional_ = "") {
238 elem.setKey(key_);
239 elem.setValue(value_);
240 elem.setConditional(conditional_);
241 this->children.push_back(elem);
242 return this->children.back();
243 }
244
246 [[nodiscard]] KV1ElementWritable& operator[](unsigned int n) {
247 return this->children.at(n);
248 }
249
251 [[nodiscard]] KV1ElementWritable& operator[](std::string_view childKey) {
252 return this->operator()(childKey);
253 }
254
256 [[nodiscard]] KV1ElementWritable& operator()(std::string_view childKey) {
257 for (auto& element : this->children) {
258 if (sourcepp::string::iequals(element.getKey(), childKey)) {
259 return element;
260 }
261 }
262 return this->addChild(childKey);
263 }
264
266 [[nodiscard]] KV1ElementWritable& operator()(std::string_view childKey, unsigned int n) {
267 unsigned int count = 0;
268 for (auto& element: this->children) {
269 if (sourcepp::string::iequals(element.getKey(), childKey)) {
270 if (count == n) {
271 return element;
272 }
273 ++count;
274 }
275 }
276 return this->addChild(childKey);
277 }
278
280 void removeChild(std::string_view childKey, int n = -1) {
281 unsigned int count = 0;
282 for (auto element = this->children.begin(); element != this->children.end(); ++element) {
283 if (sourcepp::string::iequals(element->getKey(), childKey)) {
284 if (n < 0 || count == n) {
285 element = this->children.erase(element);
286 if (count == n) {
287 return;
288 }
289 }
290 ++count;
291 }
292 }
293 }
294
295protected:
297
298 static void write(BufferStream& stream, std::vector<KV1ElementWritable>& elements, unsigned short indentLevel, const sourcepp::parser::text::EscapeSequenceMap& escapeSequences) {
299 using namespace sourcepp;
300 constexpr auto writeIndentation = [](BufferStream& stream_, unsigned short indentLevel_) {
301 for (unsigned short i = 0; i < indentLevel_; i++) {
302 stream_.write('\t');
303 }
304 };
305 constexpr auto writeQuotedString = [](BufferStream& stream_, std::string_view str, const parser::text::EscapeSequenceMap& escapeSequences_, char quoteStart = '\"', char quoteEnd = '\"') {
306 stream_.write(quoteStart);
307 if (!str.empty()) {
308 stream_.write(parser::text::convertSpecialCharsToEscapes(str, escapeSequences_), false);
309 }
310 stream_.write(quoteEnd);
311 };
312
313 for (auto& elem : elements) {
314 writeIndentation(stream, indentLevel);
315 writeQuotedString(stream, elem.key, escapeSequences);
316 if (!elem.value.empty() || elem.children.empty()) {
317 stream.write(' ');
318 writeQuotedString(stream, elem.value, escapeSequences);
319 }
320 if (!elem.conditional.empty()) {
321 stream.write(' ');
322 writeQuotedString(stream, elem.conditional, escapeSequences, '[', ']');
323 }
324 stream.write('\n');
325 if (!elem.children.empty()) {
326 writeIndentation(stream, indentLevel);
327 stream << '{' << '\n';
328 write(stream, elem.children, indentLevel + 1, escapeSequences);
329 writeIndentation(stream, indentLevel);
330 stream << '}' << '\n';
331 }
332 }
333 }
334};
335
336template<typename S = std::string>
337requires std::convertible_to<S, std::string_view>
338class KV1Writer : public KV1ElementWritable<S> {
339public:
340 explicit KV1Writer(std::string_view kv1Data = "", bool useEscapeSequences_ = false)
341 : KV1ElementWritable<S>()
342 , useEscapeSequences(useEscapeSequences_) {
343 if (kv1Data.empty()) {
344 return;
345 }
346 BufferStreamReadOnly stream{kv1Data.data(), kv1Data.size()};
347
348 // Multiply by 2 to ensure buffer will have enough space (very generous)
349 // Also it's ok that this backing data dies, it will perform copies here
350 std::string backingData(kv1Data.size() * 2, '\0');
351 BufferStream backing{backingData, false};
352 try {
354 } catch (const std::overflow_error&) {}
355 }
356
357 [[nodiscard]] std::string bake() {
358 std::string buffer;
359 BufferStream stream{buffer};
361 buffer.resize(stream.size());
362 return buffer;
363 }
364
365 void bake(const std::string& kv1Path) {
366 sourcepp::fs::writeFileText(kv1Path, this->bake());
367 }
368
369protected:
370 using KV1ElementWritable<S>::getKey;
371 using KV1ElementWritable<S>::setKey;
372 using KV1ElementWritable<S>::getValue;
373 using KV1ElementWritable<S>::setValue;
374 using KV1ElementWritable<S>::getConditional;
375 using KV1ElementWritable<S>::setConditional;
376
378};
379
380} // 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:132
static void read(BufferStreamReadOnly &stream, BufferStream &backing, std::vector< K > &elements, const sourcepp::parser::text::EscapeSequenceMap &escapeSequences)
Definition: KV1.h:137
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
bool isInvalid() const
Check if the given element is invalid.
Definition: KV1.h:125
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:96
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:101
const KV1ElementBase & operator[](unsigned int n) const
Get the child element of the element at the given index.
Definition: KV1.h:91
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:111
std::vector< K > children
Definition: KV1.h:179
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:209
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:280
KV1ElementWritable & operator=(V value_)
Set the value associated with the element.
Definition: KV1.h:225
void setConditional(std::string_view conditional_)
Set the conditional associated with the element.
Definition: KV1.h:231
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:266
KV1ElementWritable & addChild(std::string_view key_, V value_={}, std::string_view conditional_="")
Definition: KV1.h:236
void setValue(V value_)
Set the value associated with the element.
Definition: KV1.h:215
KV1ElementWritable & operator[](unsigned int n)
Get the child element of the element at the given index.
Definition: KV1.h:246
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:256
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:251
static void write(BufferStream &stream, std::vector< KV1ElementWritable > &elements, unsigned short indentLevel, const sourcepp::parser::text::EscapeSequenceMap &escapeSequences)
Definition: KV1.h:298
std::string bake()
Definition: KV1.h:357
void bake(const std::string &kv1Path)
Definition: KV1.h:365
KV1Writer(std::string_view kv1Data="", bool useEscapeSequences_=false)
Definition: KV1.h:340
bool useEscapeSequences
Definition: KV1.h:377
Definition: KV1.h:189
std::string backingData
Definition: KV1.h:198
Definition: KV1.h:13
bool writeFileText(const std::string &filepath, const std::string &text)
Definition: FS.cpp:36
const EscapeSequenceMap NO_ESCAPE_SEQUENCES
Definition: Text.cpp:26
std::unordered_map< char, char > EscapeSequenceMap
Definition: Text.h:17
const EscapeSequenceMap DEFAULT_ESCAPE_SEQUENCES
Definition: Text.cpp:12
bool isNumber(char c)
If a char is a numerical character (0-9).
Definition: Text.cpp:46
bool iequals(std::string_view s1, std::string_view s2)
Definition: String.cpp:61
Definition: LZMA.h:11