Preserve spaces in scenario strings (and other longer strings) by using CDATA
- Involves a hack-mod of ticpp::Text to support it (for some reason, the support was there in TinyXML but not exposed by ticpp) - Don't save all the intro strings if most are empty - Don't save the init special if there isn't one - Fix not saving the "I don't know" response for dialogue personalities Strings that now preserve spaces: - Descriptions of items, special items, and quests - Descriptions of special shop items that call a special node when purchased - Scenario, town, and outdoor strings - Sign strings - Journal strings - All dialogue strings (both in personalities and in talk nodes)
This commit is contained in:
@@ -758,17 +758,19 @@ Comment::Comment( const std::string& comment )
|
||||
|
||||
//*****************************************************************************
|
||||
|
||||
Text::Text()
|
||||
Text::Text(bool cdata)
|
||||
: NodeImp< TiXmlText >( new TiXmlText("") )
|
||||
{
|
||||
m_impRC->InitRef();
|
||||
if(cdata) m_tiXmlPointer->SetCDATA(true);
|
||||
}
|
||||
|
||||
|
||||
Text::Text( const std::string& value )
|
||||
Text::Text( const std::string& value, bool cdata )
|
||||
: NodeImp< TiXmlText >( new TiXmlText( value ) )
|
||||
{
|
||||
m_impRC->InitRef();
|
||||
if(cdata) m_tiXmlPointer->SetCDATA(true);
|
||||
}
|
||||
|
||||
Text::Text( TiXmlText* text )
|
||||
|
@@ -1361,7 +1361,7 @@ namespace ticpp
|
||||
/**
|
||||
Constructor.
|
||||
*/
|
||||
Text();
|
||||
explicit Text(bool cdata = false);
|
||||
|
||||
/**
|
||||
Constructor.
|
||||
@@ -1373,7 +1373,7 @@ namespace ticpp
|
||||
Constructor.
|
||||
@overload
|
||||
*/
|
||||
Text( const std::string& value );
|
||||
Text( const std::string& value, bool cdata = false );
|
||||
|
||||
/**
|
||||
Streams value into a string and creates a Text with it.
|
||||
@@ -1385,10 +1385,19 @@ namespace ticpp
|
||||
@see TiXmlText
|
||||
*/
|
||||
template < class T >
|
||||
Text( const T& value )
|
||||
Text( const T& value, bool cdata = false )
|
||||
: NodeImp< TiXmlText >( new TiXmlText( ToString( value ) ) )
|
||||
{
|
||||
m_impRC->InitRef();
|
||||
if(cdata) m_tiXmlPointer->SetCDATA(true);
|
||||
}
|
||||
|
||||
void SetCdata(bool cdata) {
|
||||
m_tiXmlPointer->SetCDATA(cdata);
|
||||
}
|
||||
|
||||
bool isCdata() const {
|
||||
return m_tiXmlPointer->CDATA();
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -32,12 +32,12 @@ namespace ticpp {
|
||||
template<typename T> void PushAttribute(std::string attrName, T attrVal) {
|
||||
openElements.top()->SetAttribute(attrName, boost::lexical_cast<std::string>(attrVal));
|
||||
}
|
||||
template<typename T> void PushText(T textVal) {
|
||||
PushNode(new Text(boost::lexical_cast<std::string>(textVal)));
|
||||
template<typename T> void PushText(T textVal, bool cdata = false) {
|
||||
PushNode(new Text(boost::lexical_cast<std::string>(textVal), cdata));
|
||||
}
|
||||
template<typename T> void PushElement(std::string tagName, T elemVal) {
|
||||
template<typename T> void PushElement(std::string tagName, T elemVal, bool cdata = false) {
|
||||
OpenElement(tagName);
|
||||
PushText(elemVal);
|
||||
PushText(elemVal, cdata);
|
||||
CloseElement(tagName);
|
||||
}
|
||||
};
|
||||
@@ -47,12 +47,12 @@ template<> inline void ticpp::Printer::PushAttribute(std::string attrName, bool
|
||||
PushAttribute(attrName, attrVal ? "true" : "false");
|
||||
}
|
||||
|
||||
template<> inline void ticpp::Printer::PushElement(std::string attrName, bool attrVal) {
|
||||
PushElement(attrName, attrVal ? "true" : "false");
|
||||
template<> inline void ticpp::Printer::PushElement(std::string attrName, bool attrVal, bool cdata) {
|
||||
PushElement(attrName, attrVal ? "true" : "false", cdata);
|
||||
}
|
||||
|
||||
template<> inline void ticpp::Printer::PushText(bool textVal) {
|
||||
PushText(textVal ? "true" : "false");
|
||||
template<> inline void ticpp::Printer::PushText(bool textVal, bool cdata) {
|
||||
PushText(textVal ? "true" : "false", cdata);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@@ -56,14 +56,14 @@ static std::string boolstr(bool b) {
|
||||
return b ? "true" : "false";
|
||||
}
|
||||
|
||||
template<> void ticpp::Printer::PushElement(std::string tagName, location pos) {
|
||||
template<> void ticpp::Printer::PushElement(std::string tagName, location pos, bool) {
|
||||
OpenElement(tagName);
|
||||
PushAttribute("x", pos.x);
|
||||
PushAttribute("y", pos.y);
|
||||
CloseElement(tagName);
|
||||
}
|
||||
|
||||
template<> void ticpp::Printer::PushElement(std::string tagName, rectangle rect) {
|
||||
template<> void ticpp::Printer::PushElement(std::string tagName, rectangle rect, bool) {
|
||||
OpenElement(tagName);
|
||||
PushAttribute("top", rect.top);
|
||||
PushAttribute("left", rect.left);
|
||||
@@ -72,7 +72,7 @@ template<> void ticpp::Printer::PushElement(std::string tagName, rectangle rect)
|
||||
CloseElement(tagName);
|
||||
}
|
||||
|
||||
template<> void ticpp::Printer::PushElement(std::string tagName, cMonster::cAttack attack) {
|
||||
template<> void ticpp::Printer::PushElement(std::string tagName, cMonster::cAttack attack, bool) {
|
||||
OpenElement(tagName);
|
||||
PushAttribute("type", attack.type);
|
||||
std::ostringstream strength;
|
||||
@@ -81,7 +81,7 @@ template<> void ticpp::Printer::PushElement(std::string tagName, cMonster::cAtta
|
||||
CloseElement(tagName);
|
||||
}
|
||||
|
||||
template<> void ticpp::Printer::PushElement(std::string tagName, cOutdoors::cWandering enc) {
|
||||
template<> void ticpp::Printer::PushElement(std::string tagName, cOutdoors::cWandering enc, bool) {
|
||||
OpenElement(tagName);
|
||||
PushAttribute("can-flee", !enc.cant_flee);
|
||||
for(size_t i = 0; i < enc.monst.size(); i++) {
|
||||
@@ -100,20 +100,16 @@ template<> void ticpp::Printer::PushElement(std::string tagName, cOutdoors::cWan
|
||||
CloseElement(tagName);
|
||||
}
|
||||
|
||||
template<> void ticpp::Printer::PushElement(std::string tagName, info_rect_t rect) {
|
||||
template<> void ticpp::Printer::PushElement(std::string tagName, info_rect_t rect, bool cdata) {
|
||||
OpenElement(tagName);
|
||||
PushAttribute("top", rect.top);
|
||||
PushAttribute("left", rect.left);
|
||||
PushAttribute("bottom", rect.bottom);
|
||||
PushAttribute("right", rect.right);
|
||||
PushText(rect.descr);
|
||||
PushText(rect.descr, cdata);
|
||||
CloseElement(tagName);
|
||||
}
|
||||
|
||||
static bool is_minmax(int lo, int hi, int val) {
|
||||
return minmax(lo, hi, val) == val;
|
||||
}
|
||||
|
||||
void writeScenarioToXml(ticpp::Printer&& data, cScenario& scenario) {
|
||||
data.OpenElement("scenario");
|
||||
data.PushAttribute("boes", scenario.format_ed_version());
|
||||
@@ -131,8 +127,15 @@ void writeScenarioToXml(ticpp::Printer&& data, cScenario& scenario) {
|
||||
data.PushElement("teaser", scenario.who_wrote[1]);
|
||||
if(scenario.intro_pic != scenario.intro_mess_pic)
|
||||
data.PushElement("icon", scenario.intro_mess_pic);
|
||||
for(int i = 0; i < 6; i++)
|
||||
data.PushElement("intro-msg", scenario.intro_strs[i]);
|
||||
{
|
||||
int last = -1;
|
||||
for(int i = 0; i < 6; i++) {
|
||||
if(!scenario.intro_strs[i].empty())
|
||||
last = i;
|
||||
}
|
||||
for(int i = 0; i <= last; i++)
|
||||
data.PushElement("intro-msg", scenario.intro_strs[i], true);
|
||||
}
|
||||
data.CloseElement("text");
|
||||
data.OpenElement("ratings");
|
||||
switch(scenario.rating) {
|
||||
@@ -158,7 +161,8 @@ void writeScenarioToXml(ticpp::Printer&& data, cScenario& scenario) {
|
||||
data.PushElement("num-towns", scenario.towns.size());
|
||||
data.PushElement("out-width", scenario.outdoors.width());
|
||||
data.PushElement("out-height", scenario.outdoors.height());
|
||||
data.PushElement("on-init", scenario.init_spec);
|
||||
if(scenario.init_spec >= 0)
|
||||
data.PushElement("on-init", scenario.init_spec);
|
||||
data.PushElement("start-town", scenario.which_town_start);
|
||||
data.PushElement("town-start", scenario.where_start);
|
||||
data.PushElement("outdoor-start", scenario.out_sec_start);
|
||||
@@ -189,7 +193,7 @@ void writeScenarioToXml(ticpp::Printer&& data, cScenario& scenario) {
|
||||
data.PushAttribute("useable", boolstr(scenario.special_items[i].flags % 10));
|
||||
data.PushAttribute("special", scenario.special_items[i].special);
|
||||
data.PushElement("name", scenario.special_items[i].name);
|
||||
data.PushElement("description", scenario.special_items[i].descr);
|
||||
data.PushElement("description", scenario.special_items[i].descr, true);
|
||||
data.CloseElement("special-item");
|
||||
}
|
||||
for(size_t i = 0; i < scenario.quests.size(); i++) {
|
||||
@@ -217,7 +221,7 @@ void writeScenarioToXml(ticpp::Printer&& data, cScenario& scenario) {
|
||||
if(quest.bank2 >= 0)
|
||||
data.PushElement("bank", quest.bank2);
|
||||
data.PushElement("name", quest.name);
|
||||
data.PushElement("description", quest.descr);
|
||||
data.PushElement("description", quest.descr, true);
|
||||
data.CloseElement("quest");
|
||||
}
|
||||
for(size_t i = 0; i < scenario.shops.size(); i++) {
|
||||
@@ -250,7 +254,7 @@ void writeScenarioToXml(ticpp::Printer&& data, cScenario& scenario) {
|
||||
case eShopItemType::CALL_SPECIAL:
|
||||
data.OpenElement("special");
|
||||
data.PushElement("name", entry.item.full_name);
|
||||
data.PushElement("description", entry.item.desc);
|
||||
data.PushElement("description", entry.item.desc, true);
|
||||
data.PushElement("node", entry.item.item_level);
|
||||
if(entry.quantity == 0)
|
||||
data.PushElement("quantity", "infinite");
|
||||
@@ -300,14 +304,14 @@ void writeScenarioToXml(ticpp::Printer&& data, cScenario& scenario) {
|
||||
if(scenario.spec_strs[i].empty()) continue;
|
||||
data.OpenElement("string");
|
||||
data.PushAttribute("id", i);
|
||||
data.PushText(scenario.spec_strs[i]);
|
||||
data.PushText(scenario.spec_strs[i], true);
|
||||
data.CloseElement("string");
|
||||
}
|
||||
for(size_t i = 0; i < scenario.journal_strs.size(); i++) {
|
||||
if(scenario.journal_strs[i].empty()) continue;
|
||||
data.OpenElement("journal");
|
||||
data.PushAttribute("id", i);
|
||||
data.PushText(scenario.journal_strs[i]);
|
||||
data.PushText(scenario.journal_strs[i], true);
|
||||
data.CloseElement("journal");
|
||||
}
|
||||
data.CloseElement("game");
|
||||
@@ -446,7 +450,7 @@ static void writeItemsToXml(ticpp::Printer&& data) {
|
||||
data.PushElement("use-flag", item.magic_use_type);
|
||||
data.CloseElement("ability");
|
||||
}
|
||||
if(!item.desc.empty()) data.PushElement("description", item.desc);
|
||||
if(!item.desc.empty()) data.PushElement("description", item.desc, true);
|
||||
data.CloseElement("item");
|
||||
}
|
||||
data.CloseElement("items");
|
||||
@@ -617,7 +621,7 @@ static void writeOutdoorsToXml(ticpp::Printer&& data, cOutdoors& sector) {
|
||||
if(sector.sign_locs[i].text.empty()) continue;
|
||||
data.OpenElement("sign");
|
||||
data.PushAttribute("id", i);
|
||||
data.PushText(sector.sign_locs[i].text);
|
||||
data.PushText(sector.sign_locs[i].text, true);
|
||||
data.CloseElement("sign");
|
||||
}
|
||||
for(auto& area : sector.info_rect) {
|
||||
@@ -628,7 +632,7 @@ static void writeOutdoorsToXml(ticpp::Printer&& data, cOutdoors& sector) {
|
||||
if(sector.spec_strs[i].empty()) continue;
|
||||
data.OpenElement("string");
|
||||
data.PushAttribute("id", i);
|
||||
data.PushText(sector.spec_strs[i]);
|
||||
data.PushText(sector.spec_strs[i], true);
|
||||
data.CloseElement("string");
|
||||
}
|
||||
data.CloseElement("sector");
|
||||
@@ -761,14 +765,14 @@ static void writeTownToXml(ticpp::Printer&& data, cTown& town) {
|
||||
if(town.sign_locs[i].text.empty()) continue;
|
||||
data.OpenElement("sign");
|
||||
data.PushAttribute("id", i);
|
||||
data.PushText(town.sign_locs[i].text);
|
||||
data.PushText(town.sign_locs[i].text, true);
|
||||
data.CloseElement("sign");
|
||||
}
|
||||
for(size_t i = 0; i < town.spec_strs.size(); i++) {
|
||||
if(town.spec_strs[i].empty()) continue;
|
||||
data.OpenElement("string");
|
||||
data.PushAttribute("id", i);
|
||||
data.PushText(town.spec_strs[i]);
|
||||
data.PushText(town.spec_strs[i], true);
|
||||
data.CloseElement("string");
|
||||
}
|
||||
data.CloseElement("town");
|
||||
@@ -781,10 +785,12 @@ static void writeDialogueToXml(ticpp::Printer&& data, cSpeech& talk, int town_nu
|
||||
cPersonality& who = talk.people[i];
|
||||
data.OpenElement("personality");
|
||||
data.PushAttribute("id", i + 10 * town_num);
|
||||
data.PushElement("title", who.title);
|
||||
data.PushElement("look", who.look);
|
||||
data.PushElement("name", who.name);
|
||||
data.PushElement("job", who.job);
|
||||
data.PushElement("title", who.title, true);
|
||||
data.PushElement("look", who.look, true);
|
||||
data.PushElement("name", who.name, true);
|
||||
data.PushElement("job", who.job, true);
|
||||
if(!who.dunno.empty())
|
||||
data.PushElement("unknown", who.dunno, true);
|
||||
data.CloseElement("personality");
|
||||
}
|
||||
for(size_t i = 0; i < talk.talk_nodes.size(); i++) {
|
||||
@@ -809,10 +815,10 @@ static void writeDialogueToXml(ticpp::Printer&& data, cSpeech& talk, int town_nu
|
||||
if(node.extras[3] >= 0)
|
||||
data.PushElement("param", node.extras[3]);
|
||||
if(!node.str1.empty())
|
||||
data.PushElement("text", node.str1);
|
||||
data.PushElement("text", node.str1, true);
|
||||
else data.PushElement("text");
|
||||
if(!node.str2.empty())
|
||||
data.PushElement("text", node.str2);
|
||||
data.PushElement("text", node.str2, true);
|
||||
data.CloseElement("node");
|
||||
}
|
||||
data.CloseElement("dialogue");
|
||||
|
51
test/files/scenario/intro_overflow.xml
Normal file
51
test/files/scenario/intro_overflow.xml
Normal file
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
|
||||
<scenario boes="2.0.0">
|
||||
<title>Test Scenario</title>
|
||||
<icon>0</icon>
|
||||
<id>campaign</id>
|
||||
<version>2.6.7</version>
|
||||
<language>en-US</language>
|
||||
<author>
|
||||
<name>BoE Test Suite</name>
|
||||
<email>nowhere@example.com</email>
|
||||
</author>
|
||||
<text>
|
||||
<teaser>Teaser 1</teaser>
|
||||
<teaser>Teaser 2</teaser>
|
||||
<intro-msg>Welcome to the test scenario!</intro-msg>
|
||||
<intro-msg></intro-msg>
|
||||
<intro-msg></intro-msg>
|
||||
<intro-msg></intro-msg>
|
||||
<intro-msg></intro-msg>
|
||||
<intro-msg></intro-msg>
|
||||
<intro-msg></intro-msg>
|
||||
</text>
|
||||
<ratings>
|
||||
<content>r</content>
|
||||
<difficulty>3</difficulty>
|
||||
</ratings>
|
||||
<flags>
|
||||
<adjust-difficulty>true</adjust-difficulty>
|
||||
<legacy>false</legacy>
|
||||
<custom-graphics>false</custom-graphics>
|
||||
</flags>
|
||||
<creator>
|
||||
<type>oboe</type>
|
||||
<version>2.0.0</version>
|
||||
<os></os>
|
||||
</creator>
|
||||
<game>
|
||||
<num-towns>0</num-towns>
|
||||
<out-width>0</out-width>
|
||||
<out-height>0</out-height>
|
||||
<start-town>7</start-town>
|
||||
<town-start x="24" y="28" />
|
||||
<outdoor-start x="1" y="3" />
|
||||
<sector-start x="12" y="21" />
|
||||
</game>
|
||||
<editor>
|
||||
<default-ground>2</default-ground>
|
||||
<last-out-section x="0" y="0" />
|
||||
<last-town>0</last-town>
|
||||
</editor>
|
||||
</scenario>
|
@@ -38,4 +38,9 @@ TEST_CASE("Loading a new-format scenario record") {
|
||||
doc = xmlDocFromStream(fin, "missing_toplevel.xml");
|
||||
REQUIRE_THROWS_AS(readScenarioFromXml(std::move(doc), scen), xMissingElem);
|
||||
}
|
||||
SECTION("When there are too many intro strings") {
|
||||
fin.open("files/scenario/intro_overflow.xml");
|
||||
doc = xmlDocFromStream(fin, "intro_overflow.xml");
|
||||
REQUIRE_THROWS_AS(readScenarioFromXml(std::move(doc), scen), xBadNode);
|
||||
}
|
||||
}
|
||||
|
@@ -129,4 +129,52 @@ TEST_CASE("Saving a scenario record") {
|
||||
CHECK(scen.special_items[0].name == "Test Special Item");
|
||||
CHECK(scen.special_items[0].descr == "This is a special item description!");
|
||||
}
|
||||
SECTION("With some empty strings, only trailing ones are stripped") {
|
||||
scen.spec_strs.resize(12);
|
||||
scen.spec_strs[3] = "Hello World!";
|
||||
scen.spec_strs[9] = "Goodbye World!";
|
||||
scen.journal_strs.resize(19);
|
||||
scen.journal_strs[7] = "My best journal!";
|
||||
scen.intro_strs[4] = "Another intro string!";
|
||||
in_and_out("empty strings", scen);
|
||||
CHECK(scen.spec_strs.size() == 10);
|
||||
CHECK(scen.spec_strs[3] == "Hello World!");
|
||||
CHECK(scen.spec_strs[9] == "Goodbye World!");
|
||||
CHECK(scen.journal_strs.size() == 8);
|
||||
CHECK(scen.journal_strs[7] == "My best journal!");
|
||||
CHECK(scen.intro_strs[4] == "Another intro string!");
|
||||
}
|
||||
SECTION("Whitespace is collapsed in short strings but not in long strings") {
|
||||
scen.scen_name = "Test Scenario with extra spaces";
|
||||
scen.who_wrote[0] = "Teaser the first!";
|
||||
scen.who_wrote[1] = " Teaser the second ! ";
|
||||
scen.spec_strs.push_back(" ");
|
||||
scen.spec_strs.push_back(" What is this... ?");
|
||||
scen.journal_strs.push_back(" Do not collapse this journal.");
|
||||
scen.intro_strs[1] = "An intro string! With extra spaces!";
|
||||
scen.special_items.emplace_back();
|
||||
scen.special_items[0].name = "A special space-filled item!";
|
||||
scen.special_items[0].descr = "A special item... with extra spaces!";
|
||||
scen.quests.emplace_back();
|
||||
scen.quests[0].name = "A quest filled with spaces... ";
|
||||
scen.quests[0].descr = "A quest... with extra spaces!";
|
||||
scen.snd_names.push_back("A sound full of spaces!");
|
||||
in_and_out("whitespace", scen);
|
||||
CHECK(scen.scen_name == "Test Scenario with extra spaces");
|
||||
CHECK(scen.who_wrote[0] == "Teaser the first!");
|
||||
CHECK(scen.who_wrote[1] == "Teaser the second !");
|
||||
CHECK(scen.spec_strs.size() == 2);
|
||||
CHECK(scen.spec_strs[0] == " ");
|
||||
CHECK(scen.spec_strs[1] == " What is this... ?");
|
||||
CHECK(scen.journal_strs.size() == 1);
|
||||
CHECK(scen.journal_strs[0] == " Do not collapse this journal.");
|
||||
CHECK(scen.intro_strs[1] == "An intro string! With extra spaces!");
|
||||
CHECK(scen.special_items.size() == 1);
|
||||
CHECK(scen.special_items[0].name == "A special space-filled item!");
|
||||
CHECK(scen.special_items[0].descr == "A special item... with extra spaces!");
|
||||
CHECK(scen.quests.size() == 1);
|
||||
CHECK(scen.quests[0].name == "A quest filled with spaces...");
|
||||
CHECK(scen.quests[0].descr == "A quest... with extra spaces!");
|
||||
CHECK(scen.snd_names[0] == "A sound full of spaces!");
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user