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:
2015-07-02 02:15:24 -04:00
parent 1f5a1118f8
commit fecd268d5a
7 changed files with 163 additions and 42 deletions

View File

@@ -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 )

View File

@@ -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();
}
};

View File

@@ -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

View File

@@ -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");

View 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>

View File

@@ -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);
}
}

View File

@@ -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!");
}
}