622 lines
18 KiB
C++
622 lines
18 KiB
C++
// SPDX-License-Identifier: LGPL-2.1-or-later
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <QString>
|
|
|
|
#ifdef _MSC_VER
|
|
#pragma warning(disable : 4996)
|
|
#pragma warning(disable : 4305)
|
|
#endif
|
|
|
|
#include "Base/Exception.h"
|
|
#include "Base/Persistence.h"
|
|
#include "Base/Reader.h"
|
|
#include <array>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <random>
|
|
#include <string>
|
|
#include <xercesc/util/PlatformUtils.hpp>
|
|
|
|
namespace fs = std::filesystem;
|
|
|
|
static std::string random_string(size_t length)
|
|
{
|
|
const std::string digits = "0123456789";
|
|
|
|
std::random_device rd;
|
|
std::mt19937 gen(rd());
|
|
std::uniform_int_distribution<> dis(0, static_cast<int>(digits.size()) - 1);
|
|
|
|
std::string result;
|
|
for (size_t i = 0; i < length; ++i) {
|
|
result += digits[dis(gen)];
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
class ReaderXML
|
|
{
|
|
public:
|
|
ReaderXML()
|
|
{
|
|
_tempDir = fs::temp_directory_path();
|
|
fs::path filename =
|
|
std::string("unit_test_Reader-") + random_string(4) + std::string(".xml");
|
|
_tempFile = _tempDir / filename;
|
|
}
|
|
~ReaderXML()
|
|
{
|
|
if (inputStream.is_open()) {
|
|
inputStream.close();
|
|
}
|
|
if (fs::exists(_tempFile)) {
|
|
fs::remove(_tempFile);
|
|
}
|
|
}
|
|
|
|
Base::XMLReader* Reader()
|
|
{
|
|
return _reader.get();
|
|
}
|
|
|
|
void givenDataAsXMLStream(const std::string& data)
|
|
{
|
|
auto stringData =
|
|
R"(<?xml version="1.0" encoding="UTF-8"?><document>)" + data + "</document>";
|
|
std::istringstream stream(stringData);
|
|
std::ofstream fileStream(_tempFile.string());
|
|
fileStream.write(stringData.data(), static_cast<std::streamsize>(stringData.length()));
|
|
fileStream.close();
|
|
inputStream.open(_tempFile.string());
|
|
_reader = std::make_unique<Base::XMLReader>(_tempFile.string().c_str(), inputStream);
|
|
}
|
|
|
|
private:
|
|
std::unique_ptr<Base::XMLReader> _reader;
|
|
fs::path _tempDir;
|
|
fs::path _tempFile;
|
|
std::ifstream inputStream;
|
|
};
|
|
|
|
class ReaderTest: public ::testing::Test
|
|
{
|
|
protected:
|
|
void SetUp() override
|
|
{
|
|
XERCES_CPP_NAMESPACE::XMLPlatformUtils::Initialize();
|
|
}
|
|
|
|
void TearDown() override
|
|
{}
|
|
};
|
|
|
|
TEST_F(ReaderTest, beginCharStreamNormal)
|
|
{
|
|
// Arrange
|
|
ReaderXML xml;
|
|
xml.givenDataAsXMLStream("<data>Test ASCII data</data>");
|
|
xml.Reader()->readElement("data");
|
|
|
|
// Act
|
|
auto& result = xml.Reader()->beginCharStream();
|
|
|
|
// Assert
|
|
EXPECT_TRUE(result.good());
|
|
}
|
|
|
|
TEST_F(ReaderTest, beginCharStreamOpenClose)
|
|
{
|
|
// Arrange
|
|
ReaderXML xml;
|
|
xml.givenDataAsXMLStream("<data id='12345' />");
|
|
xml.Reader()->readElement("data");
|
|
|
|
// Act
|
|
auto& result = xml.Reader()->beginCharStream(); // Not an error, even though there is no data
|
|
|
|
// Assert
|
|
EXPECT_TRUE(result.good());
|
|
}
|
|
|
|
TEST_F(ReaderTest, beginCharStreamAlreadyBegun)
|
|
{
|
|
// Arrange
|
|
ReaderXML xml;
|
|
xml.givenDataAsXMLStream("<data>Test ASCII data</data>");
|
|
xml.Reader()->readElement("data");
|
|
xml.Reader()->beginCharStream();
|
|
|
|
// Act & Assert
|
|
EXPECT_THROW(xml.Reader()->beginCharStream(), Base::XMLParseException); // NOLINT
|
|
}
|
|
|
|
TEST_F(ReaderTest, charStreamGood)
|
|
{
|
|
// Arrange
|
|
ReaderXML xml;
|
|
xml.givenDataAsXMLStream("<data>Test ASCII data</data>");
|
|
xml.Reader()->readElement("data");
|
|
xml.Reader()->beginCharStream();
|
|
|
|
// Act
|
|
auto& result = xml.Reader()->charStream();
|
|
|
|
// Assert
|
|
EXPECT_TRUE(result.good());
|
|
}
|
|
|
|
TEST_F(ReaderTest, charStreamBad)
|
|
{
|
|
// Arrange
|
|
ReaderXML xml;
|
|
xml.givenDataAsXMLStream("<data>Test ASCII data</data>");
|
|
xml.Reader()->readElement("data");
|
|
|
|
// Act & Assert
|
|
EXPECT_THROW(xml.Reader()->charStream(), Base::XMLParseException); // NOLINT
|
|
}
|
|
|
|
TEST_F(ReaderTest, endCharStreamGood)
|
|
{
|
|
// Arrange
|
|
ReaderXML xml;
|
|
xml.givenDataAsXMLStream("<data>Test ASCII data</data>");
|
|
xml.Reader()->readElement("data");
|
|
xml.Reader()->beginCharStream();
|
|
|
|
// Act & Assert
|
|
xml.Reader()->endCharStream(); // Does not throw
|
|
}
|
|
|
|
TEST_F(ReaderTest, endCharStreamBad)
|
|
{
|
|
// Arrange
|
|
ReaderXML xml;
|
|
xml.givenDataAsXMLStream("<data>Test ASCII data</data>");
|
|
xml.Reader()->readElement("data");
|
|
// Do not open the stream...
|
|
|
|
// Act & Assert
|
|
xml.Reader()->endCharStream(); // Does not throw, even with no open stream
|
|
}
|
|
|
|
TEST_F(ReaderTest, readDataSmallerThanBuffer)
|
|
{
|
|
// Arrange
|
|
constexpr size_t bufferSize {20};
|
|
std::string expectedData {"Test ASCII data"};
|
|
ReaderXML xml;
|
|
xml.givenDataAsXMLStream("<data>" + expectedData + "</data>");
|
|
xml.Reader()->readElement("data");
|
|
xml.Reader()->beginCharStream();
|
|
std::array<char, bufferSize> buffer {};
|
|
|
|
// Act
|
|
auto bytesRead = xml.Reader()->read(buffer.data(), bufferSize);
|
|
|
|
// Assert
|
|
EXPECT_STREQ(expectedData.c_str(), buffer.data());
|
|
EXPECT_EQ(expectedData.length(), bytesRead);
|
|
}
|
|
|
|
TEST_F(ReaderTest, readDataLargerThanBuffer)
|
|
{
|
|
// Arrange
|
|
constexpr size_t bufferSize {5};
|
|
std::string expectedData {"Test ASCII data"};
|
|
ReaderXML xml;
|
|
xml.givenDataAsXMLStream("<data>" + expectedData + "</data>");
|
|
xml.Reader()->readElement("data");
|
|
xml.Reader()->beginCharStream();
|
|
std::array<char, bufferSize> buffer {};
|
|
|
|
// Act
|
|
auto bytesRead = xml.Reader()->read(buffer.data(), bufferSize);
|
|
|
|
// Assert
|
|
for (size_t i = 0; i < bufferSize; ++i) {
|
|
EXPECT_EQ(expectedData[i], buffer.at(i));
|
|
}
|
|
EXPECT_EQ(bufferSize, bytesRead);
|
|
}
|
|
|
|
TEST_F(ReaderTest, readDataLargerThanBufferSecondRead)
|
|
{
|
|
// Arrange
|
|
constexpr size_t bufferSize {5};
|
|
std::string expectedData {"Test ASCII data"};
|
|
ReaderXML xml;
|
|
xml.givenDataAsXMLStream("<data>" + expectedData + "</data>");
|
|
xml.Reader()->readElement("data");
|
|
xml.Reader()->beginCharStream();
|
|
std::array<char, bufferSize> buffer {};
|
|
xml.Reader()->read(buffer.data(), bufferSize); // Read the first five bytes
|
|
|
|
// Act
|
|
auto bytesRead = xml.Reader()->read(buffer.data(), bufferSize); // Second five bytes
|
|
|
|
// Assert
|
|
for (size_t i = 0; i < bufferSize; ++i) {
|
|
EXPECT_EQ(expectedData[i + bufferSize], buffer.at(i));
|
|
}
|
|
EXPECT_EQ(bufferSize, bytesRead);
|
|
}
|
|
|
|
TEST_F(ReaderTest, readDataNotStarted)
|
|
{
|
|
// Arrange
|
|
constexpr size_t bufferSize {20};
|
|
std::string expectedData {"Test ASCII data"};
|
|
ReaderXML xml;
|
|
xml.givenDataAsXMLStream("<data>" + expectedData + "</data>");
|
|
xml.Reader()->readElement("data");
|
|
std::array<char, bufferSize> buffer {};
|
|
|
|
// Act
|
|
auto bytesRead = xml.Reader()->read(buffer.data(), bufferSize);
|
|
|
|
// Assert
|
|
EXPECT_EQ(-1, bytesRead); // Because we didn't call beginCharStream
|
|
}
|
|
|
|
TEST_F(ReaderTest, readNextStartElement)
|
|
{
|
|
auto xmlBody = R"(
|
|
<node1 attr='1'>Node1</node1>
|
|
<node2 attr='2'>Node2</node2>
|
|
)";
|
|
|
|
ReaderXML xml;
|
|
xml.givenDataAsXMLStream(xmlBody);
|
|
|
|
// start of document
|
|
EXPECT_TRUE(xml.Reader()->isStartOfDocument());
|
|
xml.Reader()->readElement("document");
|
|
EXPECT_STREQ(xml.Reader()->localName(), "document");
|
|
|
|
// next element
|
|
EXPECT_TRUE(xml.Reader()->readNextElement());
|
|
EXPECT_STREQ(xml.Reader()->localName(), "node1");
|
|
EXPECT_STREQ(xml.Reader()->getAttribute<const char*>("attr"), "1");
|
|
xml.Reader()->readEndElement("node1");
|
|
EXPECT_TRUE(xml.Reader()->isEndOfElement());
|
|
|
|
// next element
|
|
EXPECT_TRUE(xml.Reader()->readNextElement());
|
|
EXPECT_STREQ(xml.Reader()->localName(), "node2");
|
|
EXPECT_STREQ(xml.Reader()->getAttribute<const char*>("attr"), "2");
|
|
xml.Reader()->readEndElement("node2");
|
|
EXPECT_TRUE(xml.Reader()->isEndOfElement());
|
|
xml.Reader()->readEndElement("document");
|
|
EXPECT_TRUE(xml.Reader()->isEndOfDocument());
|
|
}
|
|
|
|
TEST_F(ReaderTest, readNextStartEndElement)
|
|
{
|
|
// Arrange
|
|
enum class TimesIGoToBed
|
|
{
|
|
Late,
|
|
Later,
|
|
VeryLate,
|
|
FreeCADDevLate // https://user-images.githubusercontent.com/12400097/235325792-606bffd6-6607-4542-a7d9-a04f12120666.png
|
|
};
|
|
|
|
auto xmlBody = R"(
|
|
<node1 attr='1'/>
|
|
<node2 attr='2'/>
|
|
<node3 attr='3'/>
|
|
<node4 attr='1'/>
|
|
<node5 attr='0'/>
|
|
<node5b attr='0xFF'/>
|
|
<node6 attr='asdaf'/>
|
|
<node7 attr="const char* is faster :'("/>
|
|
<node8 attr='8'/>
|
|
<node9 attr='9'/>
|
|
<node10 attr='10'/>
|
|
<node11 attr='11'/>
|
|
)";
|
|
|
|
ReaderXML xml;
|
|
xml.givenDataAsXMLStream(xmlBody);
|
|
|
|
// start of document
|
|
EXPECT_TRUE(xml.Reader()->isStartOfDocument());
|
|
xml.Reader()->readElement("document");
|
|
EXPECT_STREQ(xml.Reader()->localName(), "document");
|
|
|
|
// next element
|
|
EXPECT_TRUE(xml.Reader()->readNextElement());
|
|
EXPECT_STREQ(xml.Reader()->localName(), "node1");
|
|
EXPECT_STREQ(xml.Reader()->getAttribute<const char*>("attr"), "1");
|
|
|
|
// next element
|
|
EXPECT_TRUE(xml.Reader()->readNextElement());
|
|
EXPECT_STREQ(xml.Reader()->localName(), "node2");
|
|
EXPECT_STREQ(xml.Reader()->getAttribute<const char*>("attr"), "2");
|
|
|
|
// next element
|
|
EXPECT_TRUE(xml.Reader()->readNextElement());
|
|
EXPECT_STREQ(xml.Reader()->localName(), "node3");
|
|
EXPECT_EQ(xml.Reader()->getAttribute<TimesIGoToBed>("attr"), TimesIGoToBed::FreeCADDevLate);
|
|
|
|
// next element
|
|
EXPECT_TRUE(xml.Reader()->readNextElement());
|
|
EXPECT_STREQ(xml.Reader()->localName(), "node4");
|
|
EXPECT_EQ(xml.Reader()->getAttribute<bool>("attr"), true);
|
|
|
|
// next element
|
|
EXPECT_TRUE(xml.Reader()->readNextElement());
|
|
EXPECT_STREQ(xml.Reader()->localName(), "node5");
|
|
EXPECT_EQ(xml.Reader()->getAttribute<bool>("attr"), false);
|
|
|
|
// next element
|
|
EXPECT_TRUE(xml.Reader()->readNextElement());
|
|
EXPECT_STREQ(xml.Reader()->localName(), "node5b");
|
|
EXPECT_EQ(xml.Reader()->getAttribute<bool>("attr"), true);
|
|
|
|
// next element
|
|
EXPECT_TRUE(xml.Reader()->readNextElement());
|
|
EXPECT_STREQ(xml.Reader()->localName(), "node6");
|
|
EXPECT_EQ(xml.Reader()->getAttribute<bool>("attr"), true);
|
|
|
|
// next element
|
|
EXPECT_TRUE(xml.Reader()->readNextElement());
|
|
EXPECT_STREQ(xml.Reader()->localName(), "node7");
|
|
EXPECT_EQ(xml.Reader()->getAttribute<std::string>("attr"),
|
|
std::string("const char* is faster :'("));
|
|
|
|
// next element
|
|
EXPECT_TRUE(xml.Reader()->readNextElement());
|
|
EXPECT_STREQ(xml.Reader()->localName(), "node8");
|
|
EXPECT_EQ(xml.Reader()->getAttribute<long>("attr"), 8);
|
|
|
|
// next element
|
|
EXPECT_TRUE(xml.Reader()->readNextElement());
|
|
EXPECT_STREQ(xml.Reader()->localName(), "node9");
|
|
EXPECT_EQ(xml.Reader()->getAttribute<unsigned long>("attr"), 9);
|
|
|
|
// next element
|
|
EXPECT_TRUE(xml.Reader()->readNextElement());
|
|
EXPECT_STREQ(xml.Reader()->localName(), "node10");
|
|
EXPECT_EQ(xml.Reader()->getAttribute<int>("attr"), 10);
|
|
|
|
// next element
|
|
EXPECT_TRUE(xml.Reader()->readNextElement());
|
|
EXPECT_STREQ(xml.Reader()->localName(), "node11");
|
|
EXPECT_EQ(xml.Reader()->getAttribute<QString>("attr"), QStringLiteral("11"));
|
|
|
|
EXPECT_FALSE(xml.Reader()->readNextElement());
|
|
EXPECT_TRUE(xml.Reader()->isEndOfDocument());
|
|
}
|
|
|
|
TEST_F(ReaderTest, charStreamBase64Encoded)
|
|
{
|
|
// Arrange
|
|
static constexpr size_t bufferSize {100};
|
|
std::array<char, bufferSize> buffer {};
|
|
ReaderXML xml;
|
|
xml.givenDataAsXMLStream("<data>RnJlZUNBRCByb2NrcyEg8J+qqPCfqqjwn6qo\n</data>");
|
|
xml.Reader()->readElement("data");
|
|
xml.Reader()->beginCharStream(Base::CharStreamFormat::Base64Encoded);
|
|
|
|
// Act
|
|
xml.Reader()->charStream().getline(buffer.data(), bufferSize);
|
|
xml.Reader()->endCharStream();
|
|
|
|
// Assert
|
|
// Conversion done using https://www.base64encode.org for testing purposes
|
|
EXPECT_EQ(std::string("FreeCAD rocks! 🪨🪨🪨"), std::string(buffer.data()));
|
|
}
|
|
|
|
TEST_F(ReaderTest, validDefaults)
|
|
{
|
|
// Arrange
|
|
enum class TimesIGoToBed
|
|
{
|
|
Late,
|
|
Later,
|
|
VeryLate,
|
|
FreeCADDevLate // https://user-images.githubusercontent.com/12400097/235325792-606bffd6-6607-4542-a7d9-a04f12120666.png
|
|
};
|
|
|
|
auto xmlBody = R"(
|
|
<node1 attr='1'/>
|
|
<node2 attr='2'/>
|
|
)";
|
|
|
|
ReaderXML xml;
|
|
xml.givenDataAsXMLStream(xmlBody);
|
|
|
|
// Act
|
|
const char* value2 = xml.Reader()->getAttribute<const char*>("missing", "expected value");
|
|
int value4 = xml.Reader()->getAttribute<long>("missing", -123);
|
|
unsigned value6 = xml.Reader()->getAttribute<unsigned long>("missing", 123);
|
|
double value8 = xml.Reader()->getAttribute<double>("missing", 1.234);
|
|
bool value12 = xml.Reader()->getAttribute<bool>("missing", 0);
|
|
bool value14 = xml.Reader()->getAttribute<bool>("missing", 1);
|
|
bool value16 = xml.Reader()->getAttribute<bool>("missing", -10);
|
|
bool value18 = xml.Reader()->getAttribute<bool>("missing", 10);
|
|
TimesIGoToBed value20 =
|
|
xml.Reader()->getAttribute<TimesIGoToBed>("missing", TimesIGoToBed::Late);
|
|
|
|
// Assert
|
|
EXPECT_THROW({ xml.Reader()->getAttribute<const char*>("missing"); }, Base::XMLBaseException);
|
|
EXPECT_EQ(value2, "expected value");
|
|
EXPECT_THROW({ xml.Reader()->getAttribute<long>("missing"); }, Base::XMLBaseException);
|
|
EXPECT_EQ(value4, -123);
|
|
EXPECT_THROW({ xml.Reader()->getAttribute<unsigned long>("missing"); }, Base::XMLBaseException);
|
|
EXPECT_EQ(value6, 123);
|
|
EXPECT_THROW({ xml.Reader()->getAttribute<double>("missing"); }, Base::XMLBaseException);
|
|
EXPECT_NEAR(value8, 1.234, 0.001);
|
|
EXPECT_THROW({ xml.Reader()->getAttribute<int>("missing"); }, Base::XMLBaseException);
|
|
EXPECT_NEAR(value8, 1.234, 0.001);
|
|
EXPECT_THROW({ xml.Reader()->getAttribute<bool>("missing"); }, Base::XMLBaseException);
|
|
EXPECT_EQ(value12, false);
|
|
EXPECT_EQ(value14, true);
|
|
EXPECT_EQ(value16, true);
|
|
EXPECT_EQ(value18, true);
|
|
EXPECT_THROW({ xml.Reader()->getAttribute<TimesIGoToBed>("missing"); }, Base::XMLBaseException);
|
|
EXPECT_EQ(value20, TimesIGoToBed::Late);
|
|
}
|
|
|
|
TEST_F(ReaderTest, AsciiUnchanged)
|
|
{
|
|
std::string input = "Hello, world!";
|
|
std::string result = Base::Persistence::validateXMLString(input);
|
|
EXPECT_EQ(result, input);
|
|
}
|
|
|
|
TEST_F(ReaderTest, AllowedWhitespacePreserved)
|
|
{
|
|
std::string input = "a\tb\nc\rd";
|
|
std::string result = Base::Persistence::validateXMLString(input);
|
|
EXPECT_EQ(result, input);
|
|
}
|
|
|
|
TEST_F(ReaderTest, DisallowedC0ControlsBecomeUnderscore)
|
|
{
|
|
std::string input = "A";
|
|
input.push_back(char(0x00));
|
|
input.push_back(char(0x0F));
|
|
input.push_back(char(0x1B));
|
|
input += "Z";
|
|
std::string expected = "A___Z";
|
|
std::string result = Base::Persistence::validateXMLString(input);
|
|
EXPECT_EQ(result, expected);
|
|
}
|
|
|
|
TEST_F(ReaderTest, DelAndC1ControlsBecomeUnderscore)
|
|
{
|
|
std::string input;
|
|
input.push_back('X');
|
|
input.push_back(char(0x7F)); // DEL
|
|
// U+0086 (SSA) in UTF-8: 0xC2 0x86
|
|
input += std::string("\xC2\x86", 2);
|
|
input.push_back('Y');
|
|
std::string expected = "X__Y";
|
|
std::string result = Base::Persistence::validateXMLString(input);
|
|
EXPECT_EQ(result, expected);
|
|
}
|
|
|
|
namespace
|
|
{
|
|
|
|
// Minimal UTF-8 encoder -- not valid for full production use, test suite only!
|
|
static void append_cp_utf8(std::string& s, char32_t cp)
|
|
{
|
|
if (cp <= 0x7F) {
|
|
s.push_back(static_cast<char>(cp));
|
|
}
|
|
else if (cp <= 0x7FF) {
|
|
s.push_back(static_cast<char>(0xC0 | (cp >> 6)));
|
|
s.push_back(static_cast<char>(0x80 | (cp & 0x3F)));
|
|
}
|
|
else if (cp <= 0xFFFF) {
|
|
s.push_back(static_cast<char>(0xE0 | (cp >> 12)));
|
|
s.push_back(static_cast<char>(0x80 | ((cp >> 6) & 0x3F)));
|
|
s.push_back(static_cast<char>(0x80 | (cp & 0x3F)));
|
|
}
|
|
else {
|
|
s.push_back(static_cast<char>(0xF0 | (cp >> 18)));
|
|
s.push_back(static_cast<char>(0x80 | ((cp >> 12) & 0x3F)));
|
|
s.push_back(static_cast<char>(0x80 | ((cp >> 6) & 0x3F)));
|
|
s.push_back(static_cast<char>(0x80 | (cp & 0x3F)));
|
|
}
|
|
}
|
|
|
|
static std::string make_utf8(std::initializer_list<char32_t> cps)
|
|
{
|
|
std::string s;
|
|
for (char32_t cp : cps) {
|
|
append_cp_utf8(s, cp);
|
|
}
|
|
return s;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_F(ReaderTest, BmpBoundaryAndNoncharacters)
|
|
{
|
|
std::string input;
|
|
input += "X";
|
|
input += make_utf8({0xFFFD}); // allowed
|
|
input += make_utf8({0xFFFE}); // disallowed
|
|
input += "Y";
|
|
|
|
std::string expected;
|
|
expected += "X";
|
|
expected += make_utf8({0xFFFD});
|
|
expected += "_";
|
|
expected += "Y";
|
|
|
|
std::string result = Base::Persistence::validateXMLString(input);
|
|
EXPECT_EQ(result, expected);
|
|
}
|
|
|
|
TEST_F(ReaderTest, NonBmpEmojiPreserved)
|
|
{
|
|
// 😀 U+1F600
|
|
std::string emoji = make_utf8({0x1F600});
|
|
std::string input = "A" + emoji + "B";
|
|
std::string result = Base::Persistence::validateXMLString(input);
|
|
EXPECT_EQ(result, input);
|
|
}
|
|
|
|
TEST_F(ReaderTest, ZwjSequencePreserved)
|
|
{
|
|
std::string family = make_utf8({0x1F468, 0x200D, 0x1F469, 0x200D, 0x1F467, 0x200D, 0x1F466});
|
|
std::string input = "X" + family + "Y";
|
|
std::string result = Base::Persistence::validateXMLString(input);
|
|
EXPECT_EQ(result, input);
|
|
}
|
|
|
|
TEST_F(ReaderTest, CombiningMarksPreserved)
|
|
{
|
|
std::string decomposed = std::string("caf") + make_utf8({0x0065, 0x0301}); // "café"
|
|
std::string result = Base::Persistence::validateXMLString(decomposed);
|
|
EXPECT_EQ(result, decomposed);
|
|
}
|
|
|
|
TEST_F(ReaderTest, PrivateUseAreaPreserved)
|
|
{
|
|
// Yes, actually permitted by XML (as far as I can determine - chennes)
|
|
std::string pua = make_utf8({0xE000});
|
|
std::string input = "A" + pua + "B";
|
|
std::string result = Base::Persistence::validateXMLString(input);
|
|
EXPECT_EQ(result, input);
|
|
}
|
|
|
|
TEST_F(ReaderTest, MixedContentSanitization)
|
|
{
|
|
std::string input;
|
|
input += "A";
|
|
input.push_back(char(0x1F)); // disallowed control -> '_'
|
|
input += make_utf8({0x1F602}); // 😂 allowed
|
|
input.push_back(char(0x00)); // disallowed control -> '_'
|
|
input += "Z";
|
|
|
|
std::string expected;
|
|
expected += "A";
|
|
expected += "_";
|
|
expected += make_utf8({0x1F602});
|
|
expected += "_";
|
|
expected += "Z";
|
|
|
|
std::string result = Base::Persistence::validateXMLString(input);
|
|
EXPECT_EQ(result, expected);
|
|
}
|
|
|
|
TEST_F(ReaderTest, validateXmlString)
|
|
{
|
|
std::string input = "abcde";
|
|
std::string output = input;
|
|
input.push_back(char(15));
|
|
output.push_back('_');
|
|
std::string result = Base::Persistence::validateXMLString(input);
|
|
EXPECT_EQ(output, result);
|
|
}
|