TDME2  1.9.200
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
TextFormatter.cpp
Go to the documentation of this file.
2 
3 #include <string>
4 
5 #include <tdme/tdme.h>
6 #include <tdme/gui/GUIParser.h>
10 #include <tdme/utilities/Console.h>
13 #include <tdme/utilities/Float.h>
14 #include <tdme/utilities/Integer.h>
17 
18 #include <ext/tinyxml/tinyxml.h>
19 
20 #define AVOID_NULLPTR_STRING(arg) (arg == nullptr?"":arg)
21 
22 using std::string;
23 using std::to_string;
24 
26 
38 
42 
43 TextFormatter* TextFormatter::instance = nullptr;
44 
45 TextFormatter::TextFormatter() {
46  // read colors from theme
47  commentLineColor = GUIColor(GUIParser::getEngineThemeProperties()->get("color.text.commentline", "#888a85"));
48  commentInlineColor = GUIColor(GUIParser::getEngineThemeProperties()->get("color.text.commentinline", "#888a85"));
49  literalColor = GUIColor(GUIParser::getEngineThemeProperties()->get("color.text.literalcolor", "#8ae234"));
50  keyword1Color = GUIColor(GUIParser::getEngineThemeProperties()->get("color.text.keyword1color", "#cb551a"));
51  keyword2Color = GUIColor(GUIParser::getEngineThemeProperties()->get("color.text.keyword2color", "#eabc19"));
52  preprocessorColor = GUIColor(GUIParser::getEngineThemeProperties()->get("color.text.preprocessorcolor", "#ab7779"));
53  //
54  for (auto& language: languages) {
55  language.preprocessorLineKeywordsTokenized = StringTools::tokenize(language.preprocessorLineKeywords, " ");
56  language.keywords1Tokenized = StringTools::tokenize(language.keywords1, " ");
57  language.keywords2Tokenized = StringTools::tokenize(language.keywords2, " ");
58  language.datatypeLiteralSuffixesTokenized = StringTools::tokenize(language.datatypeLiteralSuffixes, " ");
59  }
60 }
61 
62 void TextFormatter::format(const string& extension, GUIStyledTextNode* textNode, int charStartIdx, int charEndIdx) {
63  if (std::find(xmlLanguage.extensions.begin(), xmlLanguage.extensions.end(), extension) != xmlLanguage.extensions.end()) {
64  textNode->unsetStyles();
65  const auto& language = xmlLanguage;
66  const auto& code = textNode->getText().getString();
67  auto startIdx = 0;
68  auto endIdx = -1;
69  auto lc = '\0';
70  auto llc = '\0';
71  auto lllc = '\0';
72  auto nc = '\0';
73  auto inlineComment = false;
74  auto quote = '\0';
75  auto tag = false;
76  auto tagMode = 0;
77  auto attribute = false;
78  for (auto i = 0; i < code.size(); i++) {
79  auto c = code[i];
80  auto nc = i + 1 < code.size()?code[i + 1]:'\0';
81  auto nnc = i + 2 < code.size()?code[i + 2]:'\0';
82  auto nnnc = i + 3 < code.size()?code[i + 3]:'\0';
83  if (tag == false && inlineComment == false && quote == '\0') {
84  if (language.commentInlineStart.empty() == false &&
85  c == language.commentInlineStart[0] &&
86  nc == language.commentInlineStart[1] &&
87  nnc == language.commentInlineStart[2] &&
88  nnnc == language.commentInlineStart[3]) {
89  inlineComment = true;
90  startIdx = i - 1;
91  endIdx = -1;
92  } else
93  if (c == '<') {
94  tag = true;
95  tagMode = 0;
96  startIdx = i;
97  endIdx = -1;
98  }
99  } else
100  if (inlineComment == true) {
101  if (language.commentInlineEnd.empty() == false &&
102  llc == language.commentInlineEnd[0] &&
103  lc == language.commentInlineEnd[1] &&
104  c == language.commentInlineEnd[2]) {
105  inlineComment = false;
106  endIdx = i + 1;
107  textNode->setTextStyle(startIdx, endIdx - 1, commentInlineColor);
108  startIdx = endIdx + 2;
109  endIdx = -1;
110  }
111  } else
112  if (quote != '\0') {
113  if (c == quote) {
114  quote = '\0';
115  endIdx = i + 1;
116  textNode->setTextStyle(startIdx, endIdx - 1, literalColor);
117  startIdx = endIdx;
118  endIdx = -1;
119  }
120  } else
121  if (tag == true) {
122  if (quote == '\0' && language.quotes.find(c) != string::npos) {
123  endIdx = i;
124  if (startIdx != endIdx) {
125  textNode->setTextStyle(startIdx, endIdx - 1, tagMode == 0?keyword1Color:keyword2Color);
126  tagMode++;
127  }
128  quote = c;
129  startIdx = i;
130  endIdx = -1;
131  } else
132  if (lc == '/' && c == '>') {
133  endIdx = i - 1;
134  if (startIdx != endIdx) {
135  textNode->setTextStyle(startIdx, endIdx - 1, tagMode == 0?keyword1Color:keyword2Color);
136  }
137  textNode->setTextStyle(endIdx, endIdx - 1 + 2, keyword1Color);
138  startIdx = i + 1;
139  endIdx = -1;
140  tag = false;
141  tagMode = 0;
142  } else
143  if (c == '>') {
144  endIdx = i;
145  if (startIdx != endIdx) {
146  textNode->setTextStyle(startIdx, endIdx - 1, tagMode == 0?keyword1Color:keyword2Color);
147  }
148  textNode->setTextStyle(endIdx, endIdx - 1 + 1, keyword1Color);
149  startIdx = i + 1;
150  endIdx = -1;
151  tag = false;
152  tagMode = 0;
153  } else
154  if (language.delimiters.find(c) != string::npos) {
155  endIdx = i;
156  if (startIdx != endIdx) {
157  textNode->setTextStyle(startIdx, endIdx - 1, tagMode == 0?keyword1Color:keyword2Color);
158  }
159  startIdx = i + 1;
160  endIdx = -1;
161  tagMode++;
162  }
163  }
164  lllc = llc;
165  llc = lc;
166  lc = c;
167  }
168  } else
169  if (std::find(propertiesLanguage.extensions.begin(), propertiesLanguage.extensions.end(), extension) != propertiesLanguage.extensions.end()) {
170  const auto& language = propertiesLanguage;
171  const auto& code = textNode->getText().getString();
172  auto commentCount = 0;
173  auto delimiterCount = 0;
174  auto nonWhitespaceCount = 0;
175  auto startIdx = -1;
176  auto endIdx = -1;
177  auto commentLine = false;
178  if (charStartIdx == -1) {
179  charStartIdx = 0;
180  } else {
181  // find index of previous newline and store difference
182  auto previousNewLineIndex = charStartIdx;
183  while (previousNewLineIndex >= 0 && code[previousNewLineIndex] != '\n') previousNewLineIndex--;
184  previousNewLineIndex = Math::min(previousNewLineIndex + 1, code.size() - 1);
185  charStartIdx = previousNewLineIndex;
186  }
187  if (charEndIdx == -1) {
188  charEndIdx = code.size() - 1;
189  } else {
190  // find index of next newline
191  auto nextNewLineIndex = Math::min(code[charEndIdx] == '\n'?charEndIdx + 1:charEndIdx, code.size() - 1);
192  while (nextNewLineIndex < code.size() && code[nextNewLineIndex] != '\n') nextNewLineIndex++;
193  nextNewLineIndex = Math::min(nextNewLineIndex + 1, code.size() - 1);
194  charEndIdx = nextNewLineIndex;
195  }
196  textNode->unsetTextStyle(charStartIdx, charEndIdx);
197  for (auto i = charStartIdx; i >= 0 && i <= charEndIdx; i++) {
198  auto c = code[i];
199  if (c == '\n') {
200  endIdx = i;
201  if (commentLine == true) {
202  if (startIdx != -1 && endIdx != -1 && startIdx != endIdx) {
203  textNode->setTextStyle(startIdx, endIdx - 1, commentLineColor);
204  }
205  } else
206  if (startIdx != -1 && endIdx != -1 && startIdx != endIdx) {
207  textNode->setTextStyle(startIdx, endIdx - 1, literalColor);
208  }
209  commentCount = 0;
210  delimiterCount = 0;
211  nonWhitespaceCount = 0;
212  startIdx = -1;
213  endIdx = -1;
214  commentLine = false;
215  } else
216  if (language.whitespaces.find(c) != string::npos) {
217  // no op
218  } else
219  if (c == language.comment && nonWhitespaceCount == 0) {
220  startIdx = i;
221  endIdx = -1;
222  commentLine = true;
223  nonWhitespaceCount++;
224  } else
225  if (c == language.delimiter && nonWhitespaceCount > 0) {
226  endIdx = i;
227  if (startIdx != -1 && startIdx != endIdx) {
228  textNode->setTextStyle(startIdx, endIdx - 1, keyword2Color);
229  }
230  startIdx = i + 1;
231  endIdx = -1;
232  nonWhitespaceCount++;
233  } else {
234  if (nonWhitespaceCount == 0) startIdx = i;
235  nonWhitespaceCount++;
236  }
237  }
238  endIdx = charEndIdx;
239  if (commentLine == true) {
240  textNode->setTextStyle(startIdx, endIdx - 1, commentLineColor);
241  } else
242  if (startIdx != -1 && startIdx != endIdx) {
243  textNode->setTextStyle(startIdx, endIdx - 1, literalColor);
244  }
245  } else {
246  auto foundLanguage = false;
247  for (const auto& language: languages) {
248  if (std::find(language.extensions.begin(), language.extensions.end(), extension) != language.extensions.end()) {
249  // Console::println("void TextFormatter::format(): " + to_string(charStartIdx) + " ... " + to_string(charEndIdx));
250  foundLanguage = true;
251  const auto& code = textNode->getText().getString();
252  const auto& preprocessorLineKeywords = language.preprocessorLineKeywordsTokenized;
253  const auto& keywords1 = language.keywords1Tokenized;
254  const auto& keywords2 = language.keywords2Tokenized;
255  const auto& datatypeLiteralSuffixes = language.datatypeLiteralSuffixesTokenized;
256  auto startIdx = 0;
257  auto endIdx = -1;
258  auto lc = '\0';
259  auto llc = '\0';
260  auto inlineComment = false;
261  auto lineComment = false;
262  auto preprocessorLine = false;
263  auto quote = '\0';
264  if (charStartIdx == -1) {
265  charStartIdx = 0;
266  } else {
267  // TODO: maybe find last quote also or last inline comment start
268  // find index of previous newline and store difference
269  auto previousNewLineIndex = Math::max(code[charStartIdx] == '\n'?charStartIdx - 1:charStartIdx, 0);
270  while (previousNewLineIndex >= 0 && code[previousNewLineIndex] != '\n') previousNewLineIndex--;
271  previousNewLineIndex = Math::min(previousNewLineIndex + 1, code.size() - 1);
272  charStartIdx = previousNewLineIndex;
273  startIdx = previousNewLineIndex;
274  }
275  if (charEndIdx == -1) {
276  charEndIdx = code.size() - 1;
277  } else {
278  // TODO: maybe find next quote also or next inline comment start
279  // find index of next newline
280  auto nextNewLineIndex = charEndIdx;
281  while (nextNewLineIndex < code.size() && code[nextNewLineIndex] != '\n') nextNewLineIndex++;
282  charEndIdx = nextNewLineIndex;
283  }
284  // Console::println("void TextFormatter::format2(): " + to_string(charStartIdx) + " ... " + to_string(charEndIdx));
285  textNode->unsetTextStyle(charStartIdx, charEndIdx);
286  lc = charStartIdx - 1 >= 0 && charStartIdx - 1 < code.size()?code[charStartIdx - 1]:'\0';
287  llc = charStartIdx - 2 >= 0 && charStartIdx - 2 < code.size()?code[charStartIdx - 2]:'\0';
288  for (auto i = charStartIdx; i >= 0 && i <= charEndIdx; i++) {
289  auto c = code[i];
290  auto nc = i + 1 < code.size()?code[i + 1]:'\0';
291  auto nnc = i + 2 < code.size()?code[i + 2]:'\0';
292  auto nnnc = i + 3 < code.size()?code[i + 3]:'\0';
293  if (inlineComment == false && lineComment == false && preprocessorLine == false && quote == '\0') {
294  if (language.commentLine.empty() == false &&
295  (c == language.commentLine[0]) &&
296  (language.commentLine.size() <= 1 || nc == language.commentLine[1]) &&
297  (language.commentLine.size() <= 2 || nnc == language.commentLine[2]) &&
298  (language.commentLine.size() <= 3 || nnnc == language.commentLine[3])) {
299  lineComment = true;
300  startIdx = i;
301  endIdx = -1;
302  } else
303  if (language.commentInlineStart.empty() == false && (language.commentInlineStart.size() == 1 || c == language.commentInlineStart[0]) && nc == language.commentInlineStart[language.commentInlineStart.size() - 1]) {
304  inlineComment = true;
305  startIdx = i;
306  endIdx = -1;
307  } else
308  if (quote == '\0' && language.keywordQuotes.find(c) != string::npos) {
309  quote = c;
310  startIdx = i;
311  endIdx = -1;
312  } else {
313  // delimiter
314  if (language.keywordDelimiters.find(c) != string::npos) {
315  endIdx = i;
316  } else
317  if (i == charEndIdx) {
318  endIdx = charEndIdx;
319  }
320  if (startIdx != -1 && endIdx != -1 && startIdx != endIdx) {
321  while (code[startIdx] == ' ' || code[startIdx] == '\t') startIdx++;
322  auto word = StringTools::trim(StringTools::substring(code, startIdx, endIdx));
323  if (word.empty() == true) continue;
324  auto literalWord = word;
325  for (const auto& datatypeLiteralSuffix: datatypeLiteralSuffixes) {
326  if (StringTools::endsWith(word, datatypeLiteralSuffix) == true) {
327  auto dotCount = 0;
328  auto valid = true;
329  for (auto j = 0; j < word.size() - datatypeLiteralSuffix.size(); j++) {
330  if (word[j] == '.') {
331  dotCount++;
332  if (dotCount > 1) {
333  valid = false;
334  break;
335  }
336  } else
337  if (isdigit(word[j]) == 0) {
338  valid = false;
339  break;
340  }
341  }
342  if (valid == true) {
343  literalWord = StringTools::substring(word, 0, word.size() - datatypeLiteralSuffix.size());
344  break;
345  }
346  }
347  }
348  if (Integer::is(literalWord) == true || Float::is(literalWord) == true) {
349  textNode->setTextStyle(startIdx, endIdx - 1, literalColor);
350  } else {
351  // Console::println("Word: '" + word + "'; " + to_string(startIdx) + " ... " + to_string(endIdx));
352  for (const auto& keyword: keywords1) {
353  if (word == keyword) {
354  textNode->setTextStyle(startIdx, endIdx - 1, keyword1Color);
355  break;
356  }
357  }
358  for (const auto& keyword: keywords2) {
359  if (word == keyword) {
360  textNode->setTextStyle(startIdx, endIdx - 1, keyword2Color);
361  break;
362  }
363  }
364  for (const auto& keyword: preprocessorLineKeywords) {
365  if (word == keyword) {
366  if (c == '\n' || i == charEndIdx) {
367  textNode->setTextStyle(startIdx, endIdx - 1, preprocessorColor);
368  } else {
369  preprocessorLine = true;
370  endIdx = startIdx - 1;
371  }
372  break;
373  }
374  }
375  }
376  startIdx = endIdx + 1;
377  endIdx = -1;
378  }
379  }
380  } else
381  if (lineComment == true) {
382  if (c == '\n' || i == charEndIdx) {
383  lineComment = false;
384  endIdx = i;
385  textNode->setTextStyle(startIdx, endIdx, commentLineColor);
386  startIdx = endIdx + 1;
387  endIdx = -1;
388  }
389  } else
390  if (inlineComment == true) {
391  if (language.commentInlineEnd.empty() == false && (language.commentInlineEnd.size() == 1 || lc == language.commentInlineEnd[0]) && c == language.commentInlineEnd[language.commentInlineEnd.size() - 1]) {
392  inlineComment = false;
393  endIdx = i;
394  textNode->setTextStyle(startIdx, endIdx - 1, commentInlineColor);
395  startIdx = endIdx + 1;
396  endIdx = -1;
397  }
398  } else
399  if (preprocessorLine == true) {
400  if (c == '\n' || i == charEndIdx) {
401  preprocessorLine = false;
402  endIdx = i;
403  textNode->setTextStyle(startIdx, endIdx, preprocessorColor);
404  startIdx = endIdx + 1;
405  endIdx = -1;
406  }
407  } else
408  if (quote != '\0') {
409  if (c == quote && (lc != '\\' || llc == '\\')) {
410  quote = '\0';endIdx = -1;
411  endIdx = i + 1;
412  textNode->setTextStyle(startIdx, endIdx - 1, literalColor);
413  startIdx = endIdx + 1;
414  endIdx = -1;
415  }
416  }
417  llc = lc;
418  lc = c;
419  }
420 
421  // done
422  break;
423  }
424  }
425  // unset styles if no language found
426  if (foundLanguage == false) textNode->unsetStyles();
427  }
428 }
429 
430 const vector<TiXmlElement*> TextFormatter::getChildrenByTagName(TiXmlElement* parent, const char* name)
431 {
432  vector<TiXmlElement*> elementList;
433  for (auto *child = parent->FirstChildElement(name); child != nullptr; child = child->NextSiblingElement(name)) {
434  elementList.push_back(child);
435  }
436  return elementList;
437 }
438 
439 const vector<TiXmlElement*> TextFormatter::getChildren(TiXmlElement* parent)
440 {
441  vector<TiXmlElement*> elementList;
442  for (auto *child = parent->FirstChildElement(); child != nullptr; child = child->NextSiblingElement()) {
443  elementList.push_back(child);
444  }
445  return elementList;
446 }
447 
449  for (const auto& language: languages) {
450  if (std::find(language.extensions.begin(), language.extensions.end(), extension) != language.extensions.end()) {
451  try {
452  // load dae xml document
453  auto xmlContent = FileSystem::getInstance()->getContentAsString("resources/engine/code-completion", StringTools::toLowerCase(language.name) + ".xml");
454  TiXmlDocument xmlDocument;
455  xmlDocument.Parse(xmlContent.c_str());
456  if (xmlDocument.Error() == true) {
457  throw ExceptionBase(string("Could not parse XML. Error='") + string(xmlDocument.ErrorDesc()) + string("'"));
458  }
459  TiXmlElement* xmlRoot = xmlDocument.RootElement();
460  auto codeCompletion = new CodeCompletion();
461  codeCompletion->name = language.name;
462  for (auto xmlKeywordElement: getChildrenByTagName(xmlRoot, "keyword")) {
463  auto symbol = CodeCompletion::CodeCompletionSymbol();
464  symbol.name = string(AVOID_NULLPTR_STRING(xmlKeywordElement->Attribute("name")));
465  for (auto xmlOverloadElement: getChildrenByTagName(xmlKeywordElement, "overload")) {
467  methodOverload.returnValue = string(AVOID_NULLPTR_STRING(xmlOverloadElement->Attribute("return-value")));
468  for (auto xmlParameterElement: getChildrenByTagName(xmlOverloadElement, "parameter")) {
469  methodOverload.parameters.push_back(string(AVOID_NULLPTR_STRING(xmlParameterElement->Attribute("name"))));
470  }
471  symbol.overloadList.push_back(methodOverload);
472  }
473  codeCompletion->symbols.push_back(symbol);
474  }
475  codeCompletion->delimiters = language.keywordDelimiters;
476  codeCompletion->statementDelimiter = language.statementDelimiter;
477  return codeCompletion;
478  } catch (Exception &exception) {
479  Console::println("TextFormatter::loadCodeCompletion(): found language: '" + language.name + "': An error occurred: " + string(exception.what()));
480  }
481  break;
482  }
483  }
484  return nullptr;
485 }
#define AVOID_NULLPTR_STRING(arg)
GUI parser.
Definition: GUIParser.h:40
void unsetTextStyle(int startIdx, int endIdx)
Unset text style.
void unsetStyles()
Unset/dispose styles.
void setTextStyle(int startIdx, int endIdx, const GUIColor &color, const string &font=string(), int size=-1, const string &url=string())
Set text style.
const MutableString & getText() const
File system singleton class.
Definition: FileSystem.h:17
const CodeCompletion * loadCodeCompletion(const string &extension)
Parse code completion.
static const vector< TiXmlElement * > getChildren(TiXmlElement *parent)
Returns immediate children tags.
void format(const string &extension, GUIStyledTextNode *textNode, int charStartIdx=-1, int charEndIdx=-1)
Format given styled text node according to extension.
static const vector< TiXmlElement * > getChildrenByTagName(TiXmlElement *parent, const char *name)
Returns immediate children tags by tag name.
struct tdme::tools::editor::misc::TextFormatter::XMLLanguage xmlLanguage
struct tdme::tools::editor::misc::TextFormatter::PropertiesLanguage propertiesLanguage
Console class.
Definition: Console.h:29
Exception base class.
Definition: ExceptionBase.h:19
Float class.
Definition: Float.h:27
Integer class.
Definition: Integer.h:25
const string & getString() const
Properties class, which helps out with storeing or loading key value pairs from/to property files.
Definition: Properties.h:23
String tools class.
Definition: StringTools.h:22
An attribute is a name-value pair.
Definition: tinyxml.h:734
Always the top level node.
Definition: tinyxml.h:1317
virtual const char * Parse(const char *p, TiXmlParsingData *data=0, TiXmlEncoding encoding=TIXML_DEFAULT_ENCODING)
Parse the given null terminated block of xml data.
const char * ErrorDesc() const
Contains a textual (english) description of the error if one occurs.
Definition: tinyxml.h:1382
const TiXmlElement * RootElement() const
Get the root element – the only top level element – of the document.
Definition: tinyxml.h:1371
bool Error() const
If an error occurs, Error will be set to true.
Definition: tinyxml.h:1379
The element is a container class.
Definition: tinyxml.h:886
const TiXmlElement * NextSiblingElement() const
Convenience function to get through elements.
Definition: tinyxml.cpp:471
const TiXmlElement * FirstChildElement() const
Convenience function to get through elements.
Definition: tinyxml.cpp:441
std::exception Exception
Exception base class.
Definition: Exception.h:18