← Back to team overview

linuxdcpp-team team mailing list archive

[Branch ~dcplusplus-team/dcplusplus/trunk] Rev 2851: XML parser: allow mixed content. HTML-RTF conv: allow nested tags, improve some

 

------------------------------------------------------------
revno: 2851
committer: poy <poy@xxxxxxxxxx>
branch nick: trunk
timestamp: Sat 2012-02-04 16:43:31 +0100
message:
  XML parser: allow mixed content. HTML-RTF conv: allow nested tags, improve some
modified:
  dcpp/ChatMessage.cpp
  dcpp/DirectoryListing.cpp
  dcpp/FavoriteManager.cpp
  dcpp/HashManager.cpp
  dcpp/QueueManager.cpp
  dcpp/ShareManager.cpp
  dcpp/SimpleXML.h
  dcpp/SimpleXMLReader.cpp
  dcpp/SimpleXMLReader.h
  test/testxml.cpp
  win32/HtmlToRtf.cpp


--
lp:dcplusplus
https://code.launchpad.net/~dcplusplus-team/dcplusplus/trunk

Your team Dcplusplus-team is subscribed to branch lp:dcplusplus.
To unsubscribe from this branch go to https://code.launchpad.net/~dcplusplus-team/dcplusplus/trunk/+edit-subscription
=== modified file 'dcpp/ChatMessage.cpp'
--- dcpp/ChatMessage.cpp	2012-02-02 23:16:07 +0000
+++ dcpp/ChatMessage.cpp	2012-02-04 15:43:31 +0000
@@ -88,29 +88,31 @@
 		return stream.str();
 	};
 
+	htmlMessage += "<span id=\"message\">";
+
 	if(BOOLSETTING(TIME_STAMPS)) {
-		tmp = "[" + Util::getShortTimeString(timestamp) + "] ";
-		message += tmp;
-		htmlMessage += addSpan("timestamp", tmp, Util::emptyString);
+		tmp = "[" + Util::getShortTimeString(timestamp) + "]";
+		message += tmp + " ";
+		htmlMessage += addSpan("timestamp", tmp, Util::emptyString) + " ";
 	}
 
 	if(messageTimestamp) {
-		tmp = "["; tmp += str(F_("Sent %1%") % Util::getShortTimeString(messageTimestamp)); tmp += "] ";
-		message += tmp;
-		htmlMessage += addSpan("messageTimestamp", tmp, Util::emptyString);
+		tmp = "["; tmp += str(F_("Sent %1%") % Util::getShortTimeString(messageTimestamp)); tmp += "]";
+		message += tmp + " ";
+		htmlMessage += addSpan("messageTimestamp", tmp, Util::emptyString) + " ";
 	}
 
 	tmp = from->getIdentity().getNick();
 	// let's *not* obey the spec here and add a space after the star. :P
-	tmp = thirdPerson ? "* " + tmp + " " : "<" + tmp + "> ";
-	message += tmp;
+	tmp = thirdPerson ? "* " + tmp + " " : "<" + tmp + ">";
+	message += tmp + " ";
 
 	auto style = from->getIdentity().getStyle();
 	string styleAttr;
 	if(!style.font.empty()) { styleAttr += "font: " + cssFont(style.font) + ";"; }
 	if(style.textColor != -1) { styleAttr += "color: #" + cssColor(style.textColor) + ";"; }
 	if(style.bgColor != -1) { styleAttr += "background-color: #" + cssColor(style.bgColor) + ";"; }
-	htmlMessage += addSpan("nick", tmp, styleAttr);
+	htmlMessage += addSpan("nick", tmp, styleAttr) + " ";
 
 	// Check all '<' and '[' after newlines as they're probably pastes...
 	tmp = text;
@@ -126,7 +128,7 @@
 	}
 
 	message += tmp;
-	htmlMessage += addSpan("message", tmp, Util::emptyString);
+	htmlMessage += addSpan("text", tmp, Util::emptyString) + "</span>";
 
 	/// @todo send this to plugins
 }

=== modified file 'dcpp/DirectoryListing.cpp'
--- dcpp/DirectoryListing.cpp	2012-01-13 20:55:20 +0000
+++ dcpp/DirectoryListing.cpp	2012-02-04 15:43:31 +0000
@@ -119,7 +119,7 @@
 	virtual ~ListLoader() { }
 
 	void startTag(const string& name, StringPairList& attribs, bool simple);
-	void endTag(const string& name, const string& data);
+	void endTag(const string& name);
 
 	const string& getBase() const { return base; }
 
@@ -219,7 +219,7 @@
 
 			if(simple) {
 				// To handle <Directory Name="..." />
-				endTag(name, Util::emptyString);
+				endTag(name);
 			}
 		}
 	} else if(name == sFileListing) {
@@ -251,12 +251,12 @@
 
 		if(simple) {
 			// To handle <Directory Name="..." />
-			endTag(name, Util::emptyString);
+			endTag(name);
 		}
 	}
 }
 
-void ListLoader::endTag(const string& name, const string&) {
+void ListLoader::endTag(const string& name) {
 	if(inListing) {
 		if(name == sDirectory) {
 			cur = cur->getParent();

=== modified file 'dcpp/FavoriteManager.cpp'
--- dcpp/FavoriteManager.cpp	2012-01-23 20:18:58 +0000
+++ dcpp/FavoriteManager.cpp	2012-02-04 15:43:31 +0000
@@ -278,8 +278,7 @@
 class XmlListLoader : public SimpleXMLReader::CallBack {
 public:
 	XmlListLoader(HubEntryList& lst) : publicHubs(lst) { }
-	virtual ~XmlListLoader() { }
-	virtual void startTag(const string& name, StringPairList& attribs, bool) {
+	void startTag(const string& name, StringPairList& attribs, bool) {
 		if(name == "Hub") {
 			const string& name = getAttrib(attribs, "Name", 0);
 			const string& server = getAttrib(attribs, "Address", 1);
@@ -297,9 +296,6 @@
 			publicHubs.push_back(HubEntry(name, server, description, users, country, shared, minShare, minSlots, maxHubs, maxUsers, reliability, rating));
 		}
 	}
-	virtual void endTag(const string&, const string&) {
-
-	}
 private:
 	HubEntryList& publicHubs;
 };

=== modified file 'dcpp/HashManager.cpp'
--- dcpp/HashManager.cpp	2012-01-23 20:18:58 +0000
+++ dcpp/HashManager.cpp	2012-02-04 15:43:31 +0000
@@ -343,8 +343,8 @@
 	HashLoader(HashManager::HashStore& s) :
 		store(s), size(0), timeStamp(0), version(HASH_FILE_VERSION), inTrees(false), inFiles(false), inHashStore(false) {
 	}
-	virtual void startTag(const string& name, StringPairList& attribs, bool simple);
-	virtual void endTag(const string& name, const string& data);
+	void startTag(const string& name, StringPairList& attribs, bool simple);
+	void endTag(const string& name);
 
 private:
 	HashManager::HashStore& store;
@@ -423,7 +423,7 @@
 	}
 }
 
-void HashLoader::endTag(const string& name, const string&) {
+void HashLoader::endTag(const string& name) {
 	if (name == sFile) {
 		file.clear();
 	}

=== modified file 'dcpp/QueueManager.cpp'
--- dcpp/QueueManager.cpp	2012-02-01 20:48:11 +0000
+++ dcpp/QueueManager.cpp	2012-02-04 15:43:31 +0000
@@ -1376,9 +1376,8 @@
 class QueueLoader : public SimpleXMLReader::CallBack {
 public:
 	QueueLoader() : cur(NULL), inDownloads(false) { }
-	virtual ~QueueLoader() { }
-	virtual void startTag(const string& name, StringPairList& attribs, bool simple);
-	virtual void endTag(const string& name, const string& data);
+	void startTag(const string& name, StringPairList& attribs, bool simple);
+	void endTag(const string& name);
 private:
 	string target;
 
@@ -1498,7 +1497,7 @@
 	}
 }
 
-void QueueLoader::endTag(const string& name, const string&) {
+void QueueLoader::endTag(const string& name) {
 	if(inDownloads) {
 		if(name == sDownload) {
 			cur = NULL;

=== modified file 'dcpp/ShareManager.cpp'
--- dcpp/ShareManager.cpp	2012-01-23 20:18:58 +0000
+++ dcpp/ShareManager.cpp	2012-02-04 15:43:31 +0000
@@ -355,7 +355,7 @@
 
 struct ShareLoader : public SimpleXMLReader::CallBack {
 	ShareLoader(ShareManager::DirList& aDirs) : dirs(aDirs), cur(0), depth(0) { }
-	virtual void startTag(const string& name, StringPairList& attribs, bool simple) {
+	void startTag(const string& name, StringPairList& attribs, bool simple) {
 		if(name == SDIRECTORY) {
 			const string& name = getAttrib(attribs, SNAME, 0);
 			if(!name.empty()) {
@@ -390,7 +390,7 @@
 			cur->files.insert(ShareManager::Directory::File(fname, Util::toInt64(size), cur, TTHValue(root)));
 		}
 	}
-	virtual void endTag(const string& name, const string&) {
+	void endTag(const string& name) {
 		if(name == SDIRECTORY) {
 			depth--;
 			if(cur) {

=== modified file 'dcpp/SimpleXML.h'
--- dcpp/SimpleXML.h	2012-01-13 20:55:20 +0000
+++ dcpp/SimpleXML.h	2012-02-04 15:43:31 +0000
@@ -203,14 +203,15 @@
 	class TagReader : public SimpleXMLReader::CallBack {
 	public:
 		TagReader(Tag* root) : cur(root) { }
-		virtual bool getData(string&) { return false; }
-		virtual void startTag(const string& name, StringPairList& attribs, bool simple) {
+		void startTag(const string& name, StringPairList& attribs, bool simple) {
 			cur->children.push_back(new Tag(name, attribs, cur));
 			if(!simple)
 				cur = cur->children.back();
 		}
-		virtual void endTag(const string&, const string& d) {
-			cur->data = d;
+		void data(const string& data) {
+			cur->data += data;
+		}
+		void endTag(const string&) {
 			if(cur->parent == NULL)
 				throw SimpleXMLException("Invalid end tag");
 			cur = cur->parent;

=== modified file 'dcpp/SimpleXMLReader.cpp'
--- dcpp/SimpleXMLReader.cpp	2012-02-01 16:26:37 +0000
+++ dcpp/SimpleXMLReader.cpp	2012-02-04 15:43:31 +0000
@@ -421,7 +421,7 @@
 			d.append(1, '\'');
 			advancePos(6);
 			return true;
-			
+
 		// Ignore &#00000 decimal and &#x0000 hex values to avoid error, they wouldn't be parsed anyway
 		} else if(charAt(1) == '#' && isdigit(charAt(2)) && charAt(3) == ';') {
 			advancePos(4);
@@ -465,12 +465,6 @@
 	}
 
 	int c = charAt(0);
-	if(c == '<') {
-		if(!value.empty()) {
-			error("Mixed content not supported");
-		}
-		return false;
-	}
 
 	if(c == '&') {
 		return entref(value);
@@ -508,11 +502,7 @@
 	}
 
 	if(charAt(0) == '>') {
-		if(!encoding.empty() && encoding != Text::utf8) {
-			value = Text::toUtf8(encoding);
-		}
-		cb->endTag(elements.back(), value);
-		value.clear();
+		cb->endTag(elements.back());
 		elements.pop_back();
 
 		state = STATE_CONTENT;
@@ -718,8 +708,11 @@
 			return true;
 		}
 
-		if(state == STATE_CONTENT && state != oldState) {
-			// might contain whitespace from previous unfruitful contents (that turned out to be elements / comments)
+		if(oldState == STATE_CONTENT && state != oldState && !value.empty()) {
+			if(!encoding.empty() && encoding != Text::utf8) {
+				value = Text::toUtf8(value, encoding);
+			}
+			cb->data(value);
 			value.clear();
 		}
 

=== modified file 'dcpp/SimpleXMLReader.h'
--- dcpp/SimpleXMLReader.h	2012-02-01 16:26:37 +0000
+++ dcpp/SimpleXMLReader.h	2012-02-04 15:43:31 +0000
@@ -29,8 +29,24 @@
 public:
 	struct CallBack : private boost::noncopyable {
 		virtual ~CallBack() { }
-		virtual void startTag(const std::string& name, StringPairList& attribs, bool simple) = 0;
-		virtual void endTag(const std::string& name, const std::string& data) = 0;
+
+		/** A new XML tag has been encountered.
+		@param name Name of the tag.
+		@param attribs List of attribute name / contents pairs representing attributes of the tag.
+		Use the getAttrib function to retrieve one particular attribute.
+		@param simple Whether this tag is void of any data (<example/>). */
+		virtual void startTag(const std::string& name, StringPairList& attribs, bool simple) { }
+
+		/** Contents of an XML tag have been read.
+		@param data Contents of the tag.
+		@note This may be called several times per tag with partial contents in mixed content
+		situations, such as: <outer>Data1<inner>Data2</inner>Data3</outer> (data will be called
+		once for "Data1", once for "Data2", once for "Data3"). */
+		virtual void data(const std::string& data) { }
+
+		/** Contents of an XML tag have been read.
+		@param name Name of the tag. */
+		virtual void endTag(const std::string& name) { }
 
 	protected:
 		static const std::string& getAttrib(StringPairList& attribs, const std::string& name, size_t hint);

=== modified file 'test/testxml.cpp'
--- test/testxml.cpp	2012-02-01 16:26:37 +0000
+++ test/testxml.cpp	2012-02-04 15:43:31 +0000
@@ -15,7 +15,7 @@
 class Collector : public SimpleXMLReader::CallBack {
 public:
 
-	virtual void startTag(const std::string& name, dcpp::StringPairList& attribs, bool simple) {
+	void startTag(const std::string& name, dcpp::StringPairList& attribs, bool simple) {
 		if(simple) {
 			simpleTags[name]++;
 		} else {
@@ -28,9 +28,12 @@
 		}
 	}
 
-	virtual void endTag(const std::string& name, const std::string& data) {
+	void data(const std::string& data) {
+		dataValues[data]++;
+	}
+
+	void endTag(const std::string& name) {
 		endTags[name]++;
-		dataValues[data]++;
 	}
 
 	Counter simpleTags;
@@ -46,7 +49,7 @@
 	Collector collector;
     SimpleXMLReader reader(&collector);
 
-    const char xml[] = "<?xml version='1.0' encoding='utf-8' ?><complex a='1'> <simple b=\"2\"/><complex2> data </complex2></complex>";
+    const char xml[] = "<?xml version='1.0' encoding='utf-8' ?><complex a='1'> data <simple b=\"2\"/><complex2> data </complex2></complex>";
     for(size_t i = 0, iend = sizeof(xml); i < iend; ++i) {
     	reader.parse(xml + i, 1);
     }
@@ -60,7 +63,7 @@
     ASSERT_EQ(collector.attribValues["2"], 1);
     ASSERT_EQ(collector.startTags["complex2"], 1);
     ASSERT_EQ(collector.endTags["complex2"], 1);
-    ASSERT_EQ(collector.dataValues[" data "], 1);
+    ASSERT_EQ(collector.dataValues[" data "], 2);
 }
 
 TEST(testxml, test_entref)

=== modified file 'win32/HtmlToRtf.cpp'
--- win32/HtmlToRtf.cpp	2012-02-02 23:16:07 +0000
+++ win32/HtmlToRtf.cpp	2012-02-04 15:43:31 +0000
@@ -22,6 +22,7 @@
 #include "HtmlToRtf.h"
 
 #include <dcpp/debug.h>
+#include <dcpp/Flags.h>
 #include <dcpp/SimpleXML.h>
 #include <dcpp/StringTokenizer.h>
 #include <dcpp/Text.h>
@@ -32,31 +33,36 @@
 struct Parser : SimpleXMLReader::CallBack {
 	Parser(dwt::RichTextBox* box);
 	void startTag(const string& name, StringPairList& attribs, bool simple);
-	void endTag(const string& name, const string& data);
+	void data(const string& data);
+	void endTag(const string& name);
 	tstring finalize();
 
 private:
-	int addFont(string&& font);
+	struct Context : Flags {
+		enum { Bold = 1 << 0, Italic = 1 << 1, Underlined = 1 << 2 };
+		size_t font; // index in the "fonts" table
+		int fontSize;
+		size_t textColor; // index in the "colors" table
+		size_t bgColor; // index in the "colors" table
+		Context(dwt::RichTextBox* box, Parser& parser);
+	};
+
+	void write(Context& context);
+
+	size_t addFont(string&& font);
 	static int rtfFontSize(float px);
-	int addColor(COLORREF color);
+	size_t addColor(COLORREF color);
 
 	void parseFont(const string& s);
-	void parseColor(int& contextColor, const string& s);
+	void parseColor(size_t& contextColor, const string& s);
+	void parseDecoration(const string& s);
 
 	tstring ret;
 
 	StringList fonts;
 	StringList colors;
 
-	struct {
-		int font;
-		int fontSize;
-		int textColor;
-		int bgColor;
-		void clear() { font = fontSize = textColor = bgColor = -1; }
-	} context, defaultContext;
-
-	string header;
+	std::deque<Context> contexts;
 };
 
 tstring HtmlToRtf::convert(const string& html, dwt::RichTextBox* box) {
@@ -67,51 +73,31 @@
 }
 
 Parser::Parser(dwt::RichTextBox* box) {
-	auto lf = box->getFont()->getLogFont();
-	defaultContext.font = addFont("{\\f0\\fnil\\fcharset" + Util::toString(lf.lfCharSet) + " " + Text::fromT(lf.lfFaceName) + ";}");
-	defaultContext.fontSize = rtfFontSize(abs(lf.lfHeight));
-
-	defaultContext.textColor = addColor(box->getTextColor());
-	defaultContext.bgColor = addColor(box->getBgColor());
-
-	context.clear();
-}
-
-int Parser::addFont(string&& font) {
-	auto ret = fonts.size();
-	fonts.push_back(std::forward<string>(font));
-	return ret;
-}
-
-int Parser::rtfFontSize(float px) {
-	return px * 72.0 / 96.0 // px -> font points
-		* dwt::util::dpiFactor() // respect DPI settings
-		* 2.0; // RTF font sizes are expressed in half-points
-}
-
-int Parser::addColor(COLORREF color) {
-	auto ret = colors.size();
-	colors.push_back("\\red" + Util::toString(GetRValue(color)) +
-		"\\green" + Util::toString(GetGValue(color)) +
-		"\\blue" + Util::toString(GetBValue(color)) + ";");
-	return ret;
-}
-
-static const string styleAttr = "style";
-static const string fontStyle = "font";
-static const string textColorStyle = "color";
-static const string bgColorStyle = "background-color";
-static string tmp;
+	// create a default context with the Rich Edit control's current formatting.
+	contexts.emplace_back(box, *this);
+	write(contexts.back());
+}
 
 void Parser::startTag(const string& name, StringPairList& attribs, bool simple) {
-	if(attribs.empty()) {
+	if(simple) {
 		return;
 	}
 
-	const auto& style = getAttrib(attribs, styleAttr, 0);
-
-	enum { Declaration, Font, TextColor, BgColor } state = Declaration;
-
+	contexts.emplace_back(contexts.back());
+
+	if(name == "b") {
+		contexts.back().setFlag(Context::Bold);
+	} else if(name == "i") {
+		contexts.back().setFlag(Context::Italic);
+	} else if(name == "u") {
+		contexts.back().setFlag(Context::Underlined);
+	}
+
+	const auto& style = getAttrib(attribs, "style", 0);
+
+	enum { Declaration, Font, Decoration, TextColor, BgColor, Unknown } state = Declaration;
+
+	string tmp;
 	size_t i = 0, j;
 	while((j = style.find_first_of(":;", i)) != string::npos) {
 		tmp = style.substr(i, j - i);
@@ -120,9 +106,11 @@
 		switch(state) {
 		case Declaration:
 			{
-				if(tmp == fontStyle) { state = Font; }
-				else if(tmp == textColorStyle) { state = TextColor; }
-				else if(tmp == bgColorStyle) { state = BgColor; }
+				if(tmp == "font") { state = Font; }
+				else if(tmp == "color") { state = TextColor; }
+				else if(tmp == "text-decoration") { state = Decoration; }
+				else if(tmp == "background-color") { state = BgColor; }
+				else { state = Unknown; }
 				break;
 			}
 
@@ -133,35 +121,45 @@
 				break;
 			}
 
+		case Decoration:
+			{
+				parseDecoration(tmp);
+				state = Declaration;
+				break;
+			}
+
 		case TextColor:
 			{
-				parseColor(context.textColor, tmp);
+				parseColor(contexts.back().textColor, tmp);
 				state = Declaration;
 				break;
 			}
 
 		case BgColor:
 			{
-				parseColor(context.bgColor, tmp);
+				parseColor(contexts.back().bgColor, tmp);
+				state = Declaration;
+				break;
+			}
+
+		case Unknown:
+			{
 				state = Declaration;
 				break;
 			}
 		}
 	}
-}
-
-void Parser::endTag(const string& name, const string& data) {
-	if(context.font == -1) { context.font = defaultContext.font; }
-	if(context.fontSize == -1) { context.fontSize = defaultContext.fontSize; }
-	if(context.textColor == -1) { context.textColor = defaultContext.textColor; }
-	if(context.bgColor == -1) { context.bgColor = defaultContext.bgColor; }
-
-	ret += Text::toT("{\\f" + Util::toString(context.font) + "\\fs" + Util::toString(context.fontSize) +
-		"\\cf" + Util::toString(context.textColor) + "\\highlight" + Util::toString(context.bgColor) +
-		header + " ") + dwt::RichTextBox::rtfEscape(Text::toT(Text::toDOS(data))) + _T("}");
-
-	context.clear();
-	header.clear();
+
+	write(contexts.back());
+}
+
+void Parser::data(const string& data) {
+	ret += dwt::RichTextBox::rtfEscape(Text::toT(Text::toDOS(data)));
+}
+
+void Parser::endTag(const string& name) {
+	ret += _T("}");
+	contexts.pop_back();
 }
 
 tstring Parser::finalize() {
@@ -169,6 +167,48 @@
 		"}{\\colortbl" + Util::toString(Util::emptyString, colors) + "}") + ret + _T("}");
 }
 
+Parser::Context::Context(dwt::RichTextBox* box, Parser& parser) {
+	// create a default context with the Rich Edit control's current formatting.
+	auto lf = box->getFont()->getLogFont();
+	font = parser.addFont("\\fnil\\fcharset" + Util::toString(lf.lfCharSet) + " " + Text::fromT(lf.lfFaceName));
+	fontSize = rtfFontSize(abs(lf.lfHeight));
+	if(lf.lfWeight >= FW_BOLD) { setFlag(Bold); }
+	if(lf.lfItalic) { setFlag(Italic); }
+
+	textColor = parser.addColor(box->getTextColor());
+	bgColor = parser.addColor(box->getBgColor());
+}
+
+void Parser::write(Context& context) {
+	string header;
+	if(context.isSet(Context::Bold)) { header += "\\b"; }
+	if(context.isSet(Context::Italic)) { header += "\\i"; }
+	if(context.isSet(Context::Underlined)) { header += "\\ul"; }
+	ret += Text::toT("{\\f" + Util::toString(context.font) + "\\fs" + Util::toString(context.fontSize) +
+		"\\cf" + Util::toString(context.textColor) + "\\highlight" + Util::toString(context.bgColor) +
+		header + " ");
+}
+
+size_t Parser::addFont(string&& font) {
+	auto ret = fonts.size();
+	fonts.push_back("{\\f" + Util::toString(ret) + std::forward<string>(font) + ";}");
+	return ret;
+}
+
+int Parser::rtfFontSize(float px) {
+	return px * 72.0 / 96.0 // px -> font points
+		* dwt::util::dpiFactor() // respect DPI settings
+		* 2.0; // RTF font sizes are expressed in half-points
+}
+
+size_t Parser::addColor(COLORREF color) {
+	auto ret = colors.size();
+	colors.push_back("\\red" + Util::toString(GetRValue(color)) +
+		"\\green" + Util::toString(GetGValue(color)) +
+		"\\blue" + Util::toString(GetBValue(color)) + ";");
+	return ret;
+}
+
 void Parser::parseFont(const string& s) {
 	// this contains multiple params separated by spaces.
 	StringTokenizer<string> tok(s, ' ');
@@ -195,32 +235,45 @@
 	family.erase(std::remove(family.begin(), family.end(), '\''), family.end());
 	if(family.empty())
 		return;
-	context.font = addFont("{\\f" + Util::toString(context.font) + "\\fnil " + std::move(family) + ";}");
+	contexts.back().font = addFont("\\fnil " + std::move(family));
 
 	// parse the second to last param (font size).
 	/// @todo handle more than px sizes
 	auto& size = *(l.end() - 2);
 	if(size.size() > 2 && *(size.end() - 2) == 'p' && *(size.end() - 1) == 'x') { // 16px
-		context.fontSize = rtfFontSize(Util::toFloat(size.substr(0, size.size() - 2)));
+		contexts.back().fontSize = rtfFontSize(Util::toFloat(size.substr(0, size.size() - 2)));
 	}
 
 	// parse the optional third to last param (font weight).
-	if(params >= 3 && Util::toInt(*(l.end() - 3)) >= FW_BOLD) {
-		header += "\\b";
+	if(params > 2 && Util::toInt(*(l.end() - 3)) >= FW_BOLD) {
+		contexts.back().setFlag(Context::Bold);
 	}
 
 	// parse the optional first param (font style).
-	if(params >= 4 && l[0] == "italic") {
-		header += "\\i";
-	}
-}
-
-void Parser::parseColor(int& contextColor, const string& s) {
+	if(params > 2 && l[0] == "italic") {
+		contexts.back().setFlag(Context::Italic);
+	}
+}
+
+void Parser::parseDecoration(const string& s) {
+	if(s.find("underline")) {
+		contexts.back().setFlag(Context::Underlined);
+	}
+}
+
+void Parser::parseColor(size_t& contextColor, const string& s) {
 	auto sharp = s.find('#');
 	if(sharp != string::npos && s.size() > sharp + 6) {
 		try {
+#if defined(__MINGW32__) && defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF)
+			/// @todo use stol on MinGW when it's available
+			unsigned int color = 0;
+			auto colStr = s.substr(sharp + 1, 6);
+			sscanf(colStr.c_str(), "%X", &color);
+#else
 			size_t pos = 0;
 			auto color = std::stol(s.substr(sharp + 1, 6), &pos, 16);
+#endif
 			contextColor = addColor(RGB((color & 0xFF0000) >> 16, (color & 0xFF00) >> 8, color & 0xFF));
 		} catch(const std::exception& e) { dcdebug("color parsing exception: %s with str: %s\n", e.what(), s.c_str()); }
 	}