Helper for creating translation files for Typica.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

main.cpp 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. // ttsg
  2. // Typica .ts Generator
  3. // This is a program that scrapes configuration files for Typica
  4. // and generates a .ts file translators can use so that user
  5. // visible text in a configuration can be translated into another
  6. // language.
  7. //
  8. // This is very slapdash with a minimal feature set focusing on
  9. // having something usable for initial translations starting with
  10. // the Typica 1.7 release. There are certain valid Typica
  11. // configurations that this will fail on. This is very much
  12. // tailored to use with the currently provided example
  13. // configuration. If you're using Typica for something strange
  14. // this tool might need to be altered.
  15. //
  16. // Copyright 2016, Neal Evan Wilson
  17. // See LICENSE for more details.
  18. #include <QCoreApplication>
  19. #include <QString>
  20. #include <QStringList>
  21. #include <QFile>
  22. #include <QFileInfo>
  23. #include <QDir>
  24. #include <QDomDocument>
  25. #include <QDomElement>
  26. #include <QXmlStreamWriter>
  27. #include <QtDebug>
  28. struct Context
  29. {
  30. QString name;
  31. QString filename;
  32. QStringList messages;
  33. QStringList locations;
  34. QString reportDirectory;
  35. };
  36. Context* buildContext(QString filename, QString basedir);
  37. int generate(QString configfile, QString transfile);
  38. int main(int argc, char **argv);
  39. // Create a Context* with messages from a file.
  40. Context* buildContext(QString filename, QString basedir)
  41. {
  42. Context *context = new Context;
  43. context->filename = filename;
  44. QFile source(QString("%1/%2").arg(basedir).arg(filename));
  45. if(source.open(QIODevice::ReadOnly))
  46. {
  47. QDomDocument document;
  48. document.setContent(&source, true);
  49. QDomElement root = document.documentElement();
  50. context->name = root.attribute("id");
  51. QDomNodeList currentSet = document.elementsByTagName("button");
  52. QDomNode currentNode;
  53. QDomElement currentElement;
  54. for(int i = 0; i < currentSet.length(); i++)
  55. {
  56. currentNode = currentSet.at(i);
  57. context->locations.append(QString("%1").arg(currentNode.lineNumber()));
  58. currentElement = currentNode.toElement();
  59. context->messages.append(currentElement.attribute("name"));
  60. }
  61. currentSet = document.elementsByTagName("reporttitle");
  62. for(int i = 0; i < currentSet.length(); i++)
  63. {
  64. currentNode = currentSet.at(i);
  65. context->locations.append(QString("%1").arg(currentNode.lineNumber()));
  66. currentElement = currentNode.toElement();
  67. context->messages.append(currentElement.text());
  68. }
  69. currentSet = document.elementsByTagName("menu");
  70. for(int i = 0; i < currentSet.length(); i++)
  71. {
  72. currentNode = currentSet.at(i);
  73. context->locations.append(QString("%1").arg(currentNode.lineNumber()));
  74. currentElement = currentNode.toElement();
  75. context->messages.append(currentElement.attribute("name"));
  76. if(currentElement.attribute("type") == "reports")
  77. {
  78. context->reportDirectory = currentElement.attribute("src");
  79. }
  80. }
  81. currentSet = document.elementsByTagName("item");
  82. for(int i = 0; i < currentSet.length(); i++)
  83. {
  84. currentNode = currentSet.at(i);
  85. context->locations.append(QString("%1").arg(currentNode.lineNumber()));
  86. currentElement = currentNode.toElement();
  87. context->messages.append(currentElement.text());
  88. }
  89. currentSet = document.elementsByTagName("label");
  90. for(int i = 0; i < currentSet.length(); i++)
  91. {
  92. currentNode = currentSet.at(i);
  93. context->locations.append(QString("%1").arg(currentNode.lineNumber()));
  94. currentElement = currentNode.toElement();
  95. context->messages.append(currentElement.text());
  96. }
  97. currentSet = document.elementsByTagName("decoration");
  98. for(int i = 0; i < currentSet.length(); i++)
  99. {
  100. currentNode = currentSet.at(i);
  101. context->locations.append(QString("%1").arg(currentNode.lineNumber()));
  102. currentElement = currentNode.toElement();
  103. context->messages.append(currentElement.attribute("name"));
  104. }
  105. currentSet = document.elementsByTagName("spinbox");
  106. for(int i = 0; i < currentSet.length(); i++)
  107. {
  108. currentNode = currentSet.at(i);
  109. currentElement = currentNode.toElement();
  110. if(currentElement.hasAttribute("pretext"))
  111. {
  112. context->locations.append(QString("%1").arg(currentNode.lineNumber()));
  113. context->messages.append(currentElement.attribute("pretext"));
  114. }
  115. if(currentElement.hasAttribute("posttext"))
  116. {
  117. context->locations.append(QString("%1").arg(currentNode.lineNumber()));
  118. context->messages.append(currentElement.attribute("posttext"));
  119. }
  120. }
  121. currentSet = document.elementsByTagName("measurementtable");
  122. for(int i = 0; i < currentSet.length(); i++)
  123. {
  124. currentNode = currentSet.at(i);
  125. currentElement = currentNode.toElement();
  126. if(currentElement.hasChildNodes())
  127. {
  128. for(int j = 0; j < currentElement.childNodes().count(); j++)
  129. {
  130. QDomNode childNode = currentElement.childNodes().at(j);
  131. if(childNode.isElement())
  132. {
  133. QDomElement childElement = childNode.toElement();
  134. if(childElement.tagName() == "column")
  135. {
  136. context->locations.append(QString("%1").arg(childNode.lineNumber()));
  137. context->messages.append(childElement.text());
  138. }
  139. }
  140. }
  141. }
  142. }
  143. currentSet = document.elementsByTagName("sqltablearray");
  144. for(int i = 0; i < currentSet.length(); i++)
  145. {
  146. currentNode = currentSet.at(i);
  147. currentElement = currentNode.toElement();
  148. if(currentElement.hasChildNodes())
  149. {
  150. for(int j = 0; j < currentElement.childNodes().count(); j++)
  151. {
  152. QDomNode childNode = currentElement.childNodes().at(j);
  153. if(childNode.isElement())
  154. {
  155. QDomElement childElement = childNode.toElement();
  156. if(childElement.tagName() == "column")
  157. {
  158. context->locations.append(QString("%1").arg(childNode.lineNumber()));
  159. context->messages.append(childElement.attribute("name"));
  160. }
  161. }
  162. }
  163. }
  164. }
  165. currentSet = document.elementsByTagName("program");
  166. for(int i = 0; i < currentSet.length(); i++)
  167. {
  168. currentNode = currentSet.at(i);
  169. currentElement = currentNode.toElement();
  170. QString script = currentElement.text();
  171. QStringList scriptLines = script.split("\n");
  172. for(int j = 0; j < scriptLines.length(); j++)
  173. {
  174. if(scriptLines.at(j).contains("TTR"))
  175. {
  176. QString currentLine = scriptLines.at(j);
  177. context->locations.append(QString("%1").arg(currentNode.lineNumber() + j + 1));
  178. currentLine = currentLine.remove(0, currentLine.indexOf("TTR"));
  179. currentLine = currentLine.remove(0, currentLine.indexOf("\"")+1);
  180. currentLine = currentLine.remove(0, currentLine.indexOf("\"")+1);
  181. currentLine = currentLine.remove(0, currentLine.indexOf("\"")+1);
  182. currentLine = currentLine.remove(currentLine.indexOf("\""), currentLine.length());
  183. context->messages.append(currentLine);
  184. }
  185. }
  186. }
  187. }
  188. source.close();
  189. return context;
  190. }
  191. // Produce a .ts file from a Typica configuration file.
  192. int generate(QString configfile, QString transfile)
  193. {
  194. QFile source(configfile);
  195. QFileInfo info(configfile);
  196. QDir directory = info.dir();
  197. if(!source.open(QIODevice::ReadOnly))
  198. {
  199. qDebug() << "Failed to open: " << configfile;
  200. return 1;
  201. }
  202. QDomDocument configuration;
  203. configuration.setContent(&source, true);
  204. QDomElement root = configuration.documentElement();
  205. QDomNodeList children = root.childNodes();
  206. QList<Context *> contexts;
  207. for(int i = 0; i < children.size(); i++)
  208. {
  209. QDomNode currentNode = children.at(i);
  210. QDomElement currentElement;
  211. if(currentNode.nodeName() == "include")
  212. {
  213. currentElement = currentNode.toElement();
  214. if(currentElement.hasAttribute("src"))
  215. {
  216. Context *context = buildContext(currentElement.attribute("src"), directory.path());
  217. if(context)
  218. {
  219. contexts.append(context);
  220. if(context->reportDirectory.length() > 0)
  221. {
  222. QDir reportsDirectory(QString("%1/%2").arg(directory.path()).arg(context->reportDirectory));
  223. reportsDirectory.setFilter(QDir::Files);
  224. reportsDirectory.setSorting(QDir::Name);
  225. QStringList nameFilter;
  226. nameFilter << "*.xml";
  227. reportsDirectory.setNameFilters(nameFilter);
  228. QStringList reportFiles = reportsDirectory.entryList();
  229. for(int j = 0; j < reportFiles.size(); j++)
  230. {
  231. Context *reportContext = buildContext(QString("%1/%2").arg(context->reportDirectory).arg(reportFiles.at(j)), directory.path());
  232. if(reportContext)
  233. {
  234. contexts.append(reportContext);
  235. }
  236. }
  237. }
  238. }
  239. }
  240. }
  241. }
  242. source.close();
  243. QFile ts(transfile);
  244. if(!ts.open(QIODevice::WriteOnly | QIODevice::Text))
  245. {
  246. qDebug() << "Failed to open output file: " << transfile;
  247. return 1;
  248. }
  249. QXmlStreamWriter xmlout(&ts);
  250. xmlout.setAutoFormatting(true);
  251. xmlout.writeStartDocument("1.0");
  252. xmlout.writeDTD("<!DOCTYPE TS>");
  253. xmlout.writeStartElement("TS");
  254. xmlout.writeAttribute("version", "2.0");
  255. foreach(Context *c, contexts)
  256. {
  257. xmlout.writeStartElement("context");
  258. xmlout.writeStartElement("name");
  259. xmlout.writeCharacters(c->name);
  260. xmlout.writeEndElement();
  261. for(int i = 0; i < c->messages.length(); i++)
  262. {
  263. xmlout.writeStartElement("message");
  264. xmlout.writeStartElement("location");
  265. xmlout.writeAttribute("filename", c->filename);
  266. xmlout.writeAttribute("line", c->locations.at(i));
  267. xmlout.writeEndElement();
  268. xmlout.writeStartElement("source");
  269. xmlout.writeCharacters(c->messages.at(i));
  270. xmlout.writeEndElement();
  271. xmlout.writeStartElement("translation");
  272. xmlout.writeCharacters("T" + c->messages.at(i));
  273. xmlout.writeEndElement();
  274. xmlout.writeEndElement();
  275. }
  276. xmlout.writeEndElement();
  277. }
  278. xmlout.writeEndElement();
  279. xmlout.writeEndDocument();
  280. ts.close();
  281. return 0;
  282. }
  283. // Take the source and destination files from the command line arguments and
  284. // pass them to generate.
  285. int main(int argc, char **argv)
  286. {
  287. QCoreApplication app(argc, argv);
  288. QStringList arguments = app.arguments();
  289. if(arguments.length() != 3) {
  290. qDebug() << "Usage: ttsg configuration.xml translation.ts";
  291. return 1;
  292. }
  293. QString configfile = arguments.at(1);
  294. QString transfile = arguments.at(2);
  295. return generate(configfile, transfile);
  296. }