! * In XML not all valid Unicode characters are allowed. Replace all * disallowed characters with '_' */
| 155 | * disallowed characters with '_' |
| 156 | */ |
| 157 | std::string Persistence::validateXMLString(const std::string& str) |
| 158 | { |
| 159 | // Decode UTF-8 into code points and filter for XML 1.0 validity, replacing invalid or |
| 160 | // discouraged code points with '_'. |
| 161 | std::string out; |
| 162 | out.reserve(str.size()); |
| 163 | |
| 164 | const auto* data = reinterpret_cast<const uint8_t*>(str.data()); // NOLINT |
| 165 | const int32_t len = static_cast<int32_t>(str.size()); |
| 166 | |
| 167 | for (int32_t i = 0; i < len;) { |
| 168 | UChar32 cp = 0; |
| 169 | U8_NEXT(data, i, len, cp); |
| 170 | if (cp < 0) { |
| 171 | out.push_back('_'); |
| 172 | continue; |
| 173 | } |
| 174 | |
| 175 | const char32_t c32 = static_cast<char32_t>(cp); |
| 176 | const bool ok = std::ranges::any_of(validRanges, [c32](const auto& r) { |
| 177 | return c32 >= r.first && c32 <= r.second; |
| 178 | }); |
| 179 | const bool discouraged = std::ranges::any_of(discouragedRanges, [c32](const auto& r) { |
| 180 | return c32 >= r.first && c32 <= r.second; |
| 181 | }); |
| 182 | |
| 183 | const char32_t emit = (ok && !discouraged) ? c32 : U'_'; |
| 184 | uint8_t buf[8] {}; |
| 185 | int32_t outLen = 0; |
| 186 | UBool isError = false; |
| 187 | U8_APPEND(buf, outLen, static_cast<int32_t>(sizeof(buf)), static_cast<UChar32>(emit), isError); |
| 188 | if (isError) { |
| 189 | out.push_back('_'); |
| 190 | } |
| 191 | else { |
| 192 | out.append(reinterpret_cast<const char*>(buf), static_cast<std::size_t>(outLen)); // NOLINT |
| 193 | } |
| 194 | } |
| 195 | |
| 196 | return out; |
| 197 | } |
| 198 | |
| 199 | void Persistence::dumpToStream(std::ostream& stream, int compression) |
| 200 | { |