| 473 | } |
| 474 | |
| 475 | void StringReader::HandleString(std::string_view src) |
| 476 | { |
| 477 | /* Ignore blank lines */ |
| 478 | if (src.empty()) return; |
| 479 | |
| 480 | StringConsumer consumer(src); |
| 481 | if (consumer.ReadCharIf('#')) { |
| 482 | if (consumer.ReadCharIf('#') && !consumer.ReadCharIf('#')) this->HandlePragma(consumer.Read(StringConsumer::npos), _strgen.lang); |
| 483 | return; // ignore comments |
| 484 | } |
| 485 | |
| 486 | /* Read string name */ |
| 487 | std::string_view str_name = StrTrimView(consumer.ReadUntilChar(':', StringConsumer::KEEP_SEPARATOR), StringConsumer::WHITESPACE_NO_NEWLINE); |
| 488 | if (!consumer.ReadCharIf(':')) { |
| 489 | StrgenError("Line has no ':' delimiter"); |
| 490 | return; |
| 491 | } |
| 492 | |
| 493 | /* Read string case */ |
| 494 | std::optional<std::string_view> casep; |
| 495 | if (auto index = str_name.find("."); index != std::string_view::npos) { |
| 496 | casep = str_name.substr(index + 1); |
| 497 | str_name = str_name.substr(0, index); |
| 498 | } |
| 499 | |
| 500 | /* Read string data */ |
| 501 | std::string_view value = consumer.Read(StringConsumer::npos); |
| 502 | |
| 503 | /* Check string is valid UTF-8 */ |
| 504 | for (StringConsumer validation_consumer(value); validation_consumer.AnyBytesLeft(); ) { |
| 505 | auto c = validation_consumer.TryReadUtf8(); |
| 506 | if (!c.has_value()) StrgenFatal("Invalid UTF-8 sequence in '{}'", value); |
| 507 | if (*c <= 0x001F || // ASCII control character range |
| 508 | *c == 0x200B || // Zero width space |
| 509 | (*c >= 0xE000 && *c <= 0xF8FF) || // Private range |
| 510 | (*c >= 0xFFF0 && *c <= 0xFFFF)) { // Specials range |
| 511 | StrgenFatal("Unwanted UTF-8 character U+{:04X} in sequence '{}'", static_cast<uint32_t>(*c), value); |
| 512 | } |
| 513 | } |
| 514 | |
| 515 | /* Check if this string already exists.. */ |
| 516 | LangString *ent = this->data.Find(str_name); |
| 517 | |
| 518 | if (this->master) { |
| 519 | if (casep.has_value()) { |
| 520 | StrgenError("Cases in the base translation are not supported."); |
| 521 | return; |
| 522 | } |
| 523 | |
| 524 | if (ent != nullptr) { |
| 525 | StrgenError("String name '{}' is used multiple times", str_name); |
| 526 | return; |
| 527 | } |
| 528 | |
| 529 | if (this->data.strings[this->data.next_string_id] != nullptr) { |
| 530 | StrgenError("String ID 0x{:X} for '{}' already in use by '{}'", this->data.next_string_id, str_name, this->data.strings[this->data.next_string_id]->name); |
| 531 | return; |
| 532 | } |
no test coverage detected