forked from mirrors/0ad
Change GUI tag parsing to use a FSM instead of using CParser. Refs #2589.
Parameters now have to be quoted with ". Supports " in parameters by escaping them with \. Tag start characters can be included in normal text by escaping them with \. Better error handling and a error messages to help with fixing invalid/malformed strings. This was SVN commit r15969.
This commit is contained in:
+274
-402
@@ -15,21 +15,16 @@
|
||||
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
GUI text
|
||||
*/
|
||||
|
||||
#include "precompiled.h"
|
||||
|
||||
#include "GUI.h"
|
||||
#include "graphics/FontMetrics.h"
|
||||
#include "ps/CLogger.h"
|
||||
#include "ps/Parser.h"
|
||||
#include <algorithm>
|
||||
|
||||
#include "GUI.h"
|
||||
#include "lib/utf8.h"
|
||||
#include "graphics/FontMetrics.h"
|
||||
#include "ps/CLogger.h"
|
||||
|
||||
|
||||
static const wchar_t TagStart = '[';
|
||||
static const wchar_t TagEnd = ']';
|
||||
// List of word demlimitor bounds
|
||||
// The list contains ranges of word delimitors. The odd indexed chars are the start
|
||||
// of a range, the even are the end of a range. The list must be sorted in INCREASING ORDER
|
||||
@@ -52,36 +47,6 @@ void CGUIString::SFeedback::Reset()
|
||||
m_NewLine=false;
|
||||
}
|
||||
|
||||
// TODO this method should be removed at one point
|
||||
// It is a solution for CParser not recognising backslash-escaped quoted
|
||||
CStrW UnescapeString(CStrW originalString)
|
||||
{
|
||||
// no reason to mess with more memory if there is nothing to unescape
|
||||
if (originalString.Find('&') == -1)
|
||||
return originalString;
|
||||
// clone the string
|
||||
CStrW newString = CStrW();
|
||||
for (size_t i=0; i < originalString.length(); ++i)
|
||||
{
|
||||
if (originalString[i] == '&' && originalString[i+1] == '#')
|
||||
{
|
||||
i += 2;
|
||||
wchar_t c = 0;
|
||||
while (originalString[i] >= '0' && originalString[i] <= '9')
|
||||
{
|
||||
c = c*10 + (originalString[i] - '0');
|
||||
++i;
|
||||
}
|
||||
ENSURE(originalString[i] == ';');
|
||||
if (c >= ' ')
|
||||
newString += c;
|
||||
}
|
||||
else
|
||||
newString += originalString[i];
|
||||
}
|
||||
return newString;
|
||||
}
|
||||
|
||||
void CGUIString::GenerateTextCall(const CGUI *pGUI,
|
||||
SFeedback &Feedback,
|
||||
CStrIntern DefaultFont,
|
||||
@@ -120,121 +85,103 @@ void CGUIString::GenerateTextCall(const CGUI *pGUI,
|
||||
// should really belong to the beginning of the next one
|
||||
if (_to == to && to >= 1)
|
||||
{
|
||||
if (GetRawString()[to-1] == ' ' ||
|
||||
GetRawString()[to-1] == '-' ||
|
||||
GetRawString()[to-1] == '\n')
|
||||
if (m_RawString[to-1] == ' ' ||
|
||||
m_RawString[to-1] == '-' ||
|
||||
m_RawString[to-1] == '\n')
|
||||
continue;
|
||||
}
|
||||
// This std::string is just a break
|
||||
if (_from == from && from >= 1)
|
||||
{
|
||||
if (GetRawString()[from] == '\n' &&
|
||||
GetRawString()[from-1] != '\n' &&
|
||||
GetRawString()[from-1] != ' ' &&
|
||||
GetRawString()[from-1] != '-')
|
||||
if (m_RawString[from] == '\n' &&
|
||||
m_RawString[from-1] != '\n' &&
|
||||
m_RawString[from-1] != ' ' &&
|
||||
m_RawString[from-1] != '-')
|
||||
continue;
|
||||
}
|
||||
|
||||
// Single tags
|
||||
if (itTextChunk->m_Tags[0].m_TagType == CGUIString::TextChunk::Tag::TAG_IMGLEFT)
|
||||
const CGUIString::TextChunk::Tag& tag = itTextChunk->m_Tags[0];
|
||||
ENSURE(tag.m_TagType == CGUIString::TextChunk::Tag::TAG_IMGLEFT
|
||||
|| tag.m_TagType == CGUIString::TextChunk::Tag::TAG_IMGRIGHT
|
||||
|| tag.m_TagType == CGUIString::TextChunk::Tag::TAG_ICON);
|
||||
|
||||
const std::string& path = utf8_from_wstring(tag.m_TagValue);
|
||||
if (!pGUI->IconExists(path))
|
||||
{
|
||||
// Only add the image if the icon exists.
|
||||
if (pGUI->IconExists(itTextChunk->m_Tags[0].m_TagValue))
|
||||
{
|
||||
Feedback.m_Images[SFeedback::Left].push_back(itTextChunk->m_Tags[0].m_TagValue);
|
||||
}
|
||||
else if (pObject)
|
||||
{
|
||||
LOGERROR(L"Trying to use an [imgleft]-tag with an undefined icon (\"%hs\").", itTextChunk->m_Tags[0].m_TagValue.c_str());
|
||||
}
|
||||
if (pObject)
|
||||
LOGERROR(L"Trying to use an icon, imgleft or imgright-tag with an undefined icon (\"%hs\").", path.c_str());
|
||||
continue;
|
||||
}
|
||||
else
|
||||
if (itTextChunk->m_Tags[0].m_TagType == CGUIString::TextChunk::Tag::TAG_IMGRIGHT)
|
||||
|
||||
switch (tag.m_TagType)
|
||||
{
|
||||
// Only add the image if the icon exists.
|
||||
if (pGUI->IconExists(itTextChunk->m_Tags[0].m_TagValue))
|
||||
case CGUIString::TextChunk::Tag::TAG_IMGLEFT:
|
||||
Feedback.m_Images[SFeedback::Left].push_back(path);
|
||||
break;
|
||||
case CGUIString::TextChunk::Tag::TAG_IMGRIGHT:
|
||||
Feedback.m_Images[SFeedback::Right].push_back(path);
|
||||
break;
|
||||
|
||||
case CGUIString::TextChunk::Tag::TAG_ICON:
|
||||
// We'll need to setup a text-call that will point
|
||||
// to the icon, this is to be able to iterate
|
||||
// through the text-calls without having to
|
||||
// complex the structure virtually for nothing more.
|
||||
SGUIText::STextCall TextCall;
|
||||
|
||||
// Also add it to the sprites being rendered.
|
||||
SGUIText::SSpriteCall SpriteCall;
|
||||
|
||||
// Get Icon from icon database in pGUI
|
||||
SGUIIcon icon = pGUI->GetIcon(path);
|
||||
|
||||
CSize size = icon.m_Size;
|
||||
|
||||
// append width, and make maximum height the height.
|
||||
Feedback.m_Size.cx += size.cx;
|
||||
Feedback.m_Size.cy = std::max(Feedback.m_Size.cy, size.cy);
|
||||
|
||||
// These are also needed later
|
||||
TextCall.m_Size = size;
|
||||
SpriteCall.m_Area = size;
|
||||
|
||||
// Handle additional attributes
|
||||
std::vector<TextChunk::Tag::TagAttribute>::const_iterator att_it;
|
||||
for (att_it = tag.m_TagAttributes.begin(); att_it != tag.m_TagAttributes.end(); ++att_it)
|
||||
{
|
||||
Feedback.m_Images[SFeedback::Right].push_back(itTextChunk->m_Tags[0].m_TagValue);
|
||||
}
|
||||
else if (pObject)
|
||||
{
|
||||
LOGERROR(L"Trying to use an [imgright]-tag with an undefined icon (\"%hs\").", itTextChunk->m_Tags[0].m_TagValue.c_str());
|
||||
}
|
||||
}
|
||||
else
|
||||
if (itTextChunk->m_Tags[0].m_TagType == CGUIString::TextChunk::Tag::TAG_ICON)
|
||||
{
|
||||
// Only add the image if the icon exists.
|
||||
if (pGUI->IconExists(itTextChunk->m_Tags[0].m_TagValue))
|
||||
{
|
||||
// We'll need to setup a text-call that will point
|
||||
// to the icon, this is to be able to iterate
|
||||
// through the text-calls without having to
|
||||
// complex the structure virtually for nothing more.
|
||||
SGUIText::STextCall TextCall;
|
||||
const TextChunk::Tag::TagAttribute& tagAttrib = *att_it;
|
||||
|
||||
// Also add it to the sprites being rendered.
|
||||
SGUIText::SSpriteCall SpriteCall;
|
||||
|
||||
// Get Icon from icon database in pGUI
|
||||
SGUIIcon icon = pGUI->GetIcon(itTextChunk->m_Tags[0].m_TagValue);
|
||||
|
||||
CSize size = icon.m_Size;
|
||||
|
||||
// append width, and make maximum height the height.
|
||||
Feedback.m_Size.cx += size.cx;
|
||||
Feedback.m_Size.cy = std::max(Feedback.m_Size.cy, size.cy);
|
||||
|
||||
// These are also needed later
|
||||
TextCall.m_Size = size;
|
||||
SpriteCall.m_Area = size;
|
||||
|
||||
// Handle additional attributes
|
||||
std::vector<TextChunk::Tag::TagAttribute>::const_iterator att_it;
|
||||
for(att_it = itTextChunk->m_Tags[0].m_TagAttributes.begin(); att_it != itTextChunk->m_Tags[0].m_TagAttributes.end(); ++att_it)
|
||||
if (tagAttrib.attrib == L"displace" && !tagAttrib.value.empty())
|
||||
{
|
||||
TextChunk::Tag::TagAttribute tagAttrib = (TextChunk::Tag::TagAttribute)(*att_it);
|
||||
|
||||
if (tagAttrib.attrib == "displace" && !tagAttrib.value.empty())
|
||||
{ //Displace the sprite
|
||||
CSize displacement;
|
||||
// Parse the value
|
||||
if (!GUI<CSize>::ParseString(CStr(tagAttrib.value).FromUTF8(), displacement))
|
||||
LOGERROR(L"Error parsing 'displace' value for tag [ICON]");
|
||||
else
|
||||
SpriteCall.m_Area += displacement;
|
||||
|
||||
}
|
||||
else if(tagAttrib.attrib == "tooltip")
|
||||
{
|
||||
SpriteCall.m_Tooltip = CStr(tagAttrib.value).FromUTF8();
|
||||
}
|
||||
else if(tagAttrib.attrib == "tooltip_style")
|
||||
{
|
||||
SpriteCall.m_TooltipStyle = CStr(tagAttrib.value).FromUTF8();
|
||||
}
|
||||
//Displace the sprite
|
||||
CSize displacement;
|
||||
// Parse the value
|
||||
if (!GUI<CSize>::ParseString(tagAttrib.value, displacement))
|
||||
LOGERROR(L"Error parsing 'displace' value for tag [ICON]");
|
||||
else
|
||||
SpriteCall.m_Area += displacement;
|
||||
}
|
||||
|
||||
SpriteCall.m_Sprite = icon.m_SpriteName;
|
||||
SpriteCall.m_CellID = icon.m_CellID;
|
||||
|
||||
// Add sprite call
|
||||
Feedback.m_SpriteCalls.push_back(SpriteCall);
|
||||
|
||||
// Finalize text call
|
||||
TextCall.m_pSpriteCall = &Feedback.m_SpriteCalls.back();
|
||||
|
||||
// Add text call
|
||||
Feedback.m_TextCalls.push_back(TextCall);
|
||||
}
|
||||
else if (pObject)
|
||||
{
|
||||
LOGERROR(L"Trying to use an [icon]-tag with an undefined icon (\"%hs\").", itTextChunk->m_Tags[0].m_TagValue.c_str());
|
||||
else if(tagAttrib.attrib == L"tooltip")
|
||||
SpriteCall.m_Tooltip = tagAttrib.value;
|
||||
else if(tagAttrib.attrib == L"tooltip_style")
|
||||
SpriteCall.m_TooltipStyle = tagAttrib.value;
|
||||
}
|
||||
|
||||
SpriteCall.m_Sprite = icon.m_SpriteName;
|
||||
SpriteCall.m_CellID = icon.m_CellID;
|
||||
|
||||
// Add sprite call
|
||||
Feedback.m_SpriteCalls.push_back(SpriteCall);
|
||||
|
||||
// Finalize text call
|
||||
TextCall.m_pSpriteCall = &Feedback.m_SpriteCalls.back();
|
||||
|
||||
// Add text call
|
||||
Feedback.m_TextCalls.push_back(TextCall);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
if (_to > _from && !Feedback.m_NewLine)
|
||||
else if (_to > _from && !Feedback.m_NewLine)
|
||||
{
|
||||
SGUIText::STextCall TextCall;
|
||||
|
||||
@@ -242,30 +189,28 @@ void CGUIString::GenerateTextCall(const CGUI *pGUI,
|
||||
TextCall.m_Font = DefaultFont;
|
||||
TextCall.m_UseCustomColor = false;
|
||||
|
||||
// Extract substd::string from RawString.
|
||||
TextCall.m_String = UnescapeString(GetRawString().substr(_from, _to-_from));
|
||||
TextCall.m_String = m_RawString.substr(_from, _to-_from);
|
||||
|
||||
// Go through tags and apply changes.
|
||||
std::vector<CGUIString::TextChunk::Tag>::const_iterator it2;
|
||||
for (it2 = itTextChunk->m_Tags.begin(); it2 != itTextChunk->m_Tags.end(); ++it2)
|
||||
{
|
||||
if (it2->m_TagType == CGUIString::TextChunk::Tag::TAG_COLOR)
|
||||
switch (it2->m_TagType)
|
||||
{
|
||||
// Set custom color
|
||||
case CGUIString::TextChunk::Tag::TAG_COLOR:
|
||||
TextCall.m_UseCustomColor = true;
|
||||
|
||||
// Try parsing the color std::string
|
||||
if (!GUI<CColor>::ParseString(CStr(it2->m_TagValue).FromUTF8(), TextCall.m_Color))
|
||||
{
|
||||
if (pObject)
|
||||
LOGERROR(L"Error parsing the value of a [color]-tag in GUI text when reading object \"%hs\".", pObject->GetPresentableName().c_str());
|
||||
}
|
||||
}
|
||||
else
|
||||
if (it2->m_TagType == CGUIString::TextChunk::Tag::TAG_FONT)
|
||||
{
|
||||
if (!GUI<CColor>::ParseString(it2->m_TagValue, TextCall.m_Color)
|
||||
&& pObject)
|
||||
LOGERROR(L"Error parsing the value of a [color]-tag in GUI text when reading object \"%hs\".", pObject->GetPresentableName().c_str());
|
||||
break;
|
||||
case CGUIString::TextChunk::Tag::TAG_FONT:
|
||||
// TODO Gee: (2004-08-15) Check if Font exists?
|
||||
TextCall.m_Font = CStrIntern(it2->m_TagValue);
|
||||
TextCall.m_Font = CStrIntern(utf8_from_wstring(it2->m_TagValue));
|
||||
break;
|
||||
default:
|
||||
LOGERROR(L"Encountered unexpected tag applied to text");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,7 +221,7 @@ void CGUIString::GenerateTextCall(const CGUI *pGUI,
|
||||
font.CalculateStringSize(TextCall.m_String.c_str(), cx, cy);
|
||||
// For anything other than the first line, the line spacing
|
||||
// needs to be considered rather than just the height of the text
|
||||
if (! FirstLine)
|
||||
if (!FirstLine)
|
||||
cy = font.GetLineSpacing();
|
||||
|
||||
size.cx = (float)cx;
|
||||
@@ -289,13 +234,8 @@ void CGUIString::GenerateTextCall(const CGUI *pGUI,
|
||||
// These are also needed later
|
||||
TextCall.m_Size = size;
|
||||
|
||||
if (! TextCall.m_String.empty())
|
||||
{
|
||||
if (TextCall.m_String[0] == '\n')
|
||||
{
|
||||
Feedback.m_NewLine = true;
|
||||
}
|
||||
}
|
||||
if (!TextCall.m_String.empty() && TextCall.m_String[0] == '\n')
|
||||
Feedback.m_NewLine = true;
|
||||
|
||||
// Add text-chunk
|
||||
Feedback.m_TextCalls.push_back(TextCall);
|
||||
@@ -303,254 +243,202 @@ void CGUIString::GenerateTextCall(const CGUI *pGUI,
|
||||
}
|
||||
}
|
||||
|
||||
bool CGUIString::TextChunk::Tag::SetTagType(const CStr& tagtype)
|
||||
bool CGUIString::TextChunk::Tag::SetTagType(const CStrW& tagtype)
|
||||
{
|
||||
CStr _tagtype = tagtype.UpperCase();
|
||||
TagType t = GetTagType(tagtype);
|
||||
if (t == TAG_INVALID)
|
||||
return false;
|
||||
|
||||
if (_tagtype == CStr("COLOR"))
|
||||
{
|
||||
m_TagType = TAG_COLOR;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
if (_tagtype == CStr("FONT"))
|
||||
{
|
||||
m_TagType = TAG_FONT;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
if (_tagtype == CStr("ICON"))
|
||||
{
|
||||
m_TagType = TAG_ICON;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
if (_tagtype == CStr("IMGLEFT"))
|
||||
{
|
||||
m_TagType = TAG_IMGLEFT;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
if (_tagtype == CStr("IMGRIGHT"))
|
||||
{
|
||||
m_TagType = TAG_IMGRIGHT;
|
||||
return true;
|
||||
}
|
||||
m_TagType = t;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
CGUIString::TextChunk::Tag::TagType CGUIString::TextChunk::Tag::GetTagType(const CStrW& tagtype)
|
||||
{
|
||||
if (tagtype == L"color")
|
||||
return TAG_COLOR;
|
||||
if (tagtype == L"font")
|
||||
return TAG_FONT;
|
||||
if (tagtype == L"icon")
|
||||
return TAG_ICON;
|
||||
if (tagtype == L"imgleft")
|
||||
return TAG_IMGLEFT;
|
||||
if (tagtype == L"imgright")
|
||||
return TAG_IMGRIGHT;
|
||||
|
||||
return TAG_INVALID;
|
||||
}
|
||||
|
||||
void CGUIString::SetValue(const CStrW& str)
|
||||
{
|
||||
m_OriginalString = str;
|
||||
|
||||
// clear
|
||||
m_TextChunks.clear();
|
||||
m_Words.clear();
|
||||
m_RawString = CStrW();
|
||||
|
||||
// Setup parser
|
||||
// TODO Gee: (2004-08-16) Create and store this parser object somewhere to save loading time.
|
||||
// TODO PT: Extended CParserCache so that the above is possible (since it currently only
|
||||
// likes one-task parsers)
|
||||
CParser Parser;
|
||||
// I've added the option of an additional parameter. Only used for icons when writing this.
|
||||
Parser.InputTaskType("start", "$ident[_=_$value_[$ident_=_$value_]]");
|
||||
Parser.InputTaskType("end", "/$ident");
|
||||
|
||||
long position = 0;
|
||||
long from=0; // the position in the raw std::string where the last tag ended
|
||||
long from_nonraw=0; // like from only in position of the REAL std::string, with tags.
|
||||
long curpos = 0;
|
||||
m_RawString.clear();
|
||||
|
||||
// Current Text Chunk
|
||||
CGUIString::TextChunk CurrentTextChunk;
|
||||
CurrentTextChunk.m_From = 0;
|
||||
|
||||
for (;;position = curpos+1)
|
||||
int l = str.length();
|
||||
int rawpos = 0;
|
||||
CStrW tag;
|
||||
std::vector<CStrW> tags;
|
||||
bool closing = false;
|
||||
for (int p = 0; p < l; ++p)
|
||||
{
|
||||
// Find next TagStart character
|
||||
curpos = str.Find(position, TagStart);
|
||||
|
||||
if (curpos == -1)
|
||||
TextChunk::Tag tag_;
|
||||
switch (str[p])
|
||||
{
|
||||
m_RawString += str.substr(position);
|
||||
|
||||
if (from != (long)m_RawString.length())
|
||||
{
|
||||
CurrentTextChunk.m_From = from;
|
||||
CurrentTextChunk.m_To = (int)m_RawString.length();
|
||||
case L'[':
|
||||
CurrentTextChunk.m_To = rawpos;
|
||||
// Add the current chunks if it is not empty
|
||||
if (CurrentTextChunk.m_From != rawpos)
|
||||
m_TextChunks.push_back(CurrentTextChunk);
|
||||
}
|
||||
CurrentTextChunk.m_From = rawpos;
|
||||
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// First check if there is another TagStart before a TagEnd,
|
||||
// in that case it's just a regular TagStart and we can continue.
|
||||
long pos_left = str.Find(curpos+1, TagStart);
|
||||
long pos_right = str.Find(curpos+1, TagEnd);
|
||||
|
||||
if (pos_right == -1)
|
||||
closing = false;
|
||||
if (++p == l)
|
||||
{
|
||||
m_RawString += str.substr(position, curpos-position+1);
|
||||
continue;
|
||||
LOGERROR(L"Partial tag at end of string '%ls'", str.c_str());
|
||||
break;
|
||||
}
|
||||
else
|
||||
if (pos_left != -1 && pos_left < pos_right)
|
||||
if (str[p] == L'/')
|
||||
{
|
||||
m_RawString += str.substr(position, pos_left-position);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_RawString += str.substr(position, curpos-position);
|
||||
|
||||
// Okay we've found a TagStart and TagEnd, positioned
|
||||
// at pos and pos_right. Now let's extract the
|
||||
// interior and try parsing.
|
||||
CStrW tagstr (str.substr(curpos+1, pos_right-curpos-1));
|
||||
|
||||
CParserLine Line;
|
||||
Line.ParseString(Parser, tagstr.ToUTF8());
|
||||
|
||||
// Set to true if the tag is just text.
|
||||
bool justtext = false;
|
||||
|
||||
if (Line.m_ParseOK)
|
||||
closing = true;
|
||||
if (tags.empty())
|
||||
{
|
||||
if (Line.m_TaskTypeName == "start")
|
||||
LOGERROR(L"Encountered closing tag without having any open tags. At %d in '%ls'", p, str.c_str());
|
||||
break;
|
||||
}
|
||||
if (++p == l)
|
||||
{
|
||||
LOGERROR(L"Partial closing tag at end of string '%ls'", str.c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
tag.clear();
|
||||
// Parse tag
|
||||
for (; p < l && str[p] != L']'; ++p)
|
||||
{
|
||||
CStrW name, param;
|
||||
switch (str[p])
|
||||
{
|
||||
case L' ':
|
||||
if (closing) // We still parse them to make error handling cleaner
|
||||
LOGERROR(L"Closing tags do not support parameters (at pos %d '%ls')", p, str.c_str());
|
||||
|
||||
// parse something="something else"
|
||||
for (++p; p < l && str[p] != L'='; ++p)
|
||||
name.push_back(str[p]);
|
||||
|
||||
if (p == l)
|
||||
{
|
||||
// The tag
|
||||
TextChunk::Tag tag;
|
||||
std::string Str_TagType;
|
||||
LOGERROR(L"Parameter without value at pos %d '%ls'", p, str.c_str());
|
||||
break;
|
||||
}
|
||||
// fall-through
|
||||
case L'=':
|
||||
// parse a quoted parameter
|
||||
if (closing) // We still parse them to make error handling cleaner
|
||||
LOGERROR(L"Closing tags do not support parameters (at pos %d '%ls')", p, str.c_str());
|
||||
|
||||
Line.GetArgString(0, Str_TagType);
|
||||
|
||||
if (!tag.SetTagType(Str_TagType))
|
||||
if (++p == l)
|
||||
{
|
||||
LOGERROR(L"Expected parameter, got end of string '%ls'", str.c_str());
|
||||
break;
|
||||
}
|
||||
if (str[p] != L'"')
|
||||
{
|
||||
LOGERROR(L"Unquoted parameters are not supported (at pos %d '%ls')", p, str.c_str());
|
||||
break;
|
||||
}
|
||||
for (++p; p < l && str[p] != L'"'; ++p)
|
||||
{
|
||||
switch (str[p])
|
||||
{
|
||||
justtext = true;
|
||||
case L'\\':
|
||||
if (++p == l)
|
||||
{
|
||||
LOGERROR(L"Escape character at end of string '%ls'", str.c_str());
|
||||
break;
|
||||
}
|
||||
// fall-through
|
||||
default:
|
||||
param.push_back(str[p]);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Check for possible value-std::strings
|
||||
if (Line.GetArgCount() >= 2)
|
||||
Line.GetArgString(1, tag.m_TagValue);
|
||||
}
|
||||
|
||||
//Handle arbitrary number of additional parameters
|
||||
size_t argn;
|
||||
for(argn = 2; argn < Line.GetArgCount(); argn += 2)
|
||||
{
|
||||
TextChunk::Tag::TagAttribute a;
|
||||
|
||||
Line.GetArgString(argn, a.attrib);
|
||||
Line.GetArgString(argn+1, a.value);
|
||||
|
||||
tag.m_TagAttributes.push_back(a);
|
||||
}
|
||||
|
||||
// Finalize last
|
||||
if (curpos != from_nonraw)
|
||||
{
|
||||
CurrentTextChunk.m_From = from;
|
||||
CurrentTextChunk.m_To = from + curpos - from_nonraw;
|
||||
m_TextChunks.push_back(CurrentTextChunk);
|
||||
from = CurrentTextChunk.m_To;
|
||||
}
|
||||
from_nonraw = pos_right+1;
|
||||
|
||||
// Some tags does not have a closure, and should be
|
||||
// stored without text. Like a <tag /> in XML.
|
||||
if (tag.m_TagType == TextChunk::Tag::TAG_IMGLEFT ||
|
||||
tag.m_TagType == TextChunk::Tag::TAG_IMGRIGHT ||
|
||||
tag.m_TagType == TextChunk::Tag::TAG_ICON)
|
||||
{
|
||||
// We need to use a fresh text chunk
|
||||
// because 'tag' should be the *only* tag.
|
||||
TextChunk FreshTextChunk;
|
||||
|
||||
// They does not work with the text system.
|
||||
FreshTextChunk.m_From = from + pos_right+1 - from_nonraw;
|
||||
FreshTextChunk.m_To = from + pos_right+1 - from_nonraw;
|
||||
|
||||
FreshTextChunk.m_Tags.push_back(tag);
|
||||
|
||||
m_TextChunks.push_back(FreshTextChunk);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add that tag, but first, erase previous occurences of the
|
||||
// same tag.
|
||||
std::vector<TextChunk::Tag>::iterator it;
|
||||
for (it = CurrentTextChunk.m_Tags.begin(); it != CurrentTextChunk.m_Tags.end(); ++it)
|
||||
{
|
||||
if (it->m_TagType == tag.m_TagType)
|
||||
{
|
||||
CurrentTextChunk.m_Tags.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add!
|
||||
CurrentTextChunk.m_Tags.push_back(tag);
|
||||
}
|
||||
}
|
||||
if (!name.empty())
|
||||
{
|
||||
TextChunk::Tag::TagAttribute a = {name, param};
|
||||
tag_.m_TagAttributes.push_back(a);
|
||||
}
|
||||
else
|
||||
if (Line.m_TaskTypeName == "end")
|
||||
{
|
||||
// The tag
|
||||
TextChunk::Tag tag;
|
||||
std::string Str_TagType;
|
||||
|
||||
Line.GetArgString(0, Str_TagType);
|
||||
|
||||
if (!tag.SetTagType(Str_TagType))
|
||||
{
|
||||
justtext = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Finalize the previous chunk
|
||||
if (curpos != from_nonraw)
|
||||
{
|
||||
CurrentTextChunk.m_From = from;
|
||||
CurrentTextChunk.m_To = from + curpos - from_nonraw;
|
||||
m_TextChunks.push_back(CurrentTextChunk);
|
||||
from = CurrentTextChunk.m_To;
|
||||
}
|
||||
from_nonraw = pos_right+1;
|
||||
|
||||
// Search for the tag, if it's not added, then
|
||||
// pass it as plain text.
|
||||
std::vector<TextChunk::Tag>::iterator it;
|
||||
for (it = CurrentTextChunk.m_Tags.begin(); it != CurrentTextChunk.m_Tags.end(); ++it)
|
||||
{
|
||||
if (it->m_TagType == tag.m_TagType)
|
||||
{
|
||||
CurrentTextChunk.m_Tags.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
tag_.m_TagValue = param;
|
||||
break;
|
||||
default:
|
||||
tag.push_back(str[p]);
|
||||
break;
|
||||
}
|
||||
else justtext = true;
|
||||
|
||||
if (justtext)
|
||||
{
|
||||
// What was within the tags could not be interpreted
|
||||
// so we'll assume it's just text.
|
||||
m_RawString += str.substr(curpos, pos_right-curpos+1);
|
||||
}
|
||||
|
||||
curpos = pos_right;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!tag_.SetTagType(tag))
|
||||
{
|
||||
LOGERROR(L"Invalid tag '%ls' at %d in '%ls'", tag.c_str(), p, str.c_str());
|
||||
break;
|
||||
}
|
||||
if (!closing)
|
||||
{
|
||||
if (tag_.m_TagType == TextChunk::Tag::TAG_IMGRIGHT
|
||||
|| tag_.m_TagType == TextChunk::Tag::TAG_IMGLEFT
|
||||
|| tag_.m_TagType == TextChunk::Tag::TAG_ICON)
|
||||
{
|
||||
TextChunk FreshTextChunk = { rawpos, rawpos };
|
||||
FreshTextChunk.m_Tags.push_back(tag_);
|
||||
m_TextChunks.push_back(FreshTextChunk);
|
||||
}
|
||||
else
|
||||
{
|
||||
tags.push_back(tag);
|
||||
CurrentTextChunk.m_Tags.push_back(tag_);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (tag != tags.back())
|
||||
{
|
||||
LOGERROR(L"Closing tag '%ls' does not match last opened tag '%ls' at %d in '%ls'", tag.c_str(), tags.back().c_str(), p, str.c_str());
|
||||
break;
|
||||
}
|
||||
|
||||
tags.pop_back();
|
||||
CurrentTextChunk.m_Tags.pop_back();
|
||||
}
|
||||
break;
|
||||
case L'\\':
|
||||
if (++p == l)
|
||||
{
|
||||
LOGERROR(L"Escape character at end of string '%ls'", str.c_str());
|
||||
break;
|
||||
}
|
||||
// fall-through
|
||||
default:
|
||||
++rawpos;
|
||||
m_RawString.push_back(str[p]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the chunk after the last tag
|
||||
if (CurrentTextChunk.m_From != rawpos)
|
||||
{
|
||||
CurrentTextChunk.m_To = rawpos;
|
||||
m_TextChunks.push_back(CurrentTextChunk);
|
||||
}
|
||||
|
||||
|
||||
// Add a delimiter at start and at end, it helps when
|
||||
// processing later, because we don't have make exceptions for
|
||||
// those cases.
|
||||
@@ -581,37 +469,21 @@ void CGUIString::SetValue(const CStrW& str)
|
||||
m_Words.push_back((int)m_RawString.length());
|
||||
|
||||
// Remove duplicates (only if larger than 2)
|
||||
if (m_Words.size() > 2)
|
||||
if (m_Words.size() <= 2)
|
||||
return;
|
||||
|
||||
std::vector<int>::iterator it;
|
||||
int last_word = -1;
|
||||
for (it = m_Words.begin(); it != m_Words.end(); )
|
||||
{
|
||||
std::vector<int>::iterator it;
|
||||
int last_word = -1;
|
||||
for (it = m_Words.begin(); it != m_Words.end(); )
|
||||
if (last_word == *it)
|
||||
{
|
||||
if (last_word == *it)
|
||||
{
|
||||
it = m_Words.erase(it);
|
||||
}
|
||||
else
|
||||
{
|
||||
last_word = *it;
|
||||
++it;
|
||||
}
|
||||
it = m_Words.erase(it);
|
||||
}
|
||||
else
|
||||
{
|
||||
last_word = *it;
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
for (int i=0; i<(int)m_Words.size(); ++i)
|
||||
{
|
||||
LOGMESSAGE(L"m_Words[%d] = %d", i, m_Words[i]);
|
||||
}
|
||||
|
||||
for (int i=0; i<(int)m_TextChunks.size(); ++i)
|
||||
{
|
||||
LOGMESSAGE(L"m_TextChunk[%d] = [%d,%d]", i, m_TextChunks[i].m_From, m_TextChunks[i].m_To);
|
||||
for (int j=0; j<(int)m_TextChunks[i].m_Tags.size(); ++j)
|
||||
{
|
||||
LOGMESSAGE(L"--Tag: %d \"%hs\"", (int)m_TextChunks[i].m_Tags[j].m_TagType, m_TextChunks[i].m_Tags[j].m_TagValue.c_str());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
+14
-16
@@ -187,7 +187,7 @@ public:
|
||||
struct TextChunk
|
||||
{
|
||||
/**
|
||||
* A tag looks like this "Hello [B]there[/B] little"
|
||||
* A tag looks like this "Hello [b]there[/b] little"
|
||||
*/
|
||||
struct Tag
|
||||
{
|
||||
@@ -203,34 +203,37 @@ public:
|
||||
TAG_COLOR,
|
||||
TAG_IMGLEFT,
|
||||
TAG_IMGRIGHT,
|
||||
TAG_ICON
|
||||
TAG_ICON,
|
||||
TAG_INVALID
|
||||
};
|
||||
|
||||
struct TagAttribute
|
||||
{
|
||||
std::string attrib;
|
||||
std::string value;
|
||||
std::wstring attrib;
|
||||
std::wstring value;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set tag from string
|
||||
*
|
||||
* @param tagtype TagType by string, like 'IMG' for [IMG]
|
||||
* @param tagtype TagType by string, like 'img' for [img]
|
||||
* @return True if m_TagType was set.
|
||||
*/
|
||||
bool SetTagType(const CStr& tagtype);
|
||||
bool SetTagType(const CStrW& tagtype);
|
||||
TagType GetTagType(const CStrW& tagtype);
|
||||
|
||||
|
||||
/**
|
||||
* In [B=Hello][/B]
|
||||
* In [b="Hello"][/b]
|
||||
* m_TagType is TAG_B
|
||||
*/
|
||||
TagType m_TagType;
|
||||
|
||||
/**
|
||||
* In [B=Hello][/B]
|
||||
* In [b="Hello"][/b]
|
||||
* m_TagValue is 'Hello'
|
||||
*/
|
||||
std::string m_TagValue;
|
||||
std::wstring m_TagValue;
|
||||
|
||||
/**
|
||||
* Some tags need an additional attributes
|
||||
@@ -244,7 +247,7 @@ public:
|
||||
int m_From, m_To;
|
||||
|
||||
/**
|
||||
* Tags that are present. [A][B]
|
||||
* Tags that are present. [a][b]
|
||||
*/
|
||||
std::vector<Tag> m_Tags;
|
||||
};
|
||||
@@ -292,11 +295,6 @@ public:
|
||||
*/
|
||||
void SetValue(const CStrW& str);
|
||||
|
||||
/**
|
||||
* Get String, without tags
|
||||
*/
|
||||
const CStrW& GetRawString() const { return m_RawString; }
|
||||
|
||||
/**
|
||||
* Get String, with tags
|
||||
*/
|
||||
@@ -333,12 +331,12 @@ public:
|
||||
*/
|
||||
std::vector<int> m_Words;
|
||||
|
||||
private:
|
||||
/**
|
||||
* TextChunks
|
||||
*/
|
||||
std::vector<TextChunk> m_TextChunks;
|
||||
|
||||
private:
|
||||
/**
|
||||
* The full raw string. Stripped of tags.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user