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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  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. #include <QHash>
  29. #include <QHashIterator>
  30. struct Context
  31. {
  32. QString name;
  33. QString filename;
  34. QStringList messages;
  35. QStringList locations;
  36. QString reportDirectory;
  37. };
  38. struct Locations
  39. {
  40. QStringList filenames;
  41. QStringList linenumbers;
  42. };
  43. QHash<QString, Locations*> globalContext;
  44. void addMessage(QString source, QString file, int line);
  45. Context* buildContext(QString filename, QString basedir);
  46. int generate(QString configfile, QString transfile);
  47. int main(int argc, char **argv);
  48. // Add a message to the context.
  49. void addMessage(QString source, QString file, int line)
  50. {
  51. Locations *location;
  52. if(globalContext.contains(source))
  53. {
  54. location = globalContext.value(source);
  55. }
  56. else
  57. {
  58. location = new Locations;
  59. }
  60. location->filenames.append(file);
  61. location->linenumbers.append(QString("%1").arg(line));
  62. globalContext.insert(source, location);
  63. }
  64. // Create a Context* with messages from a file.
  65. Context* buildContext(QString filename, QString basedir)
  66. {
  67. Context *context = new Context;
  68. context->filename = filename;
  69. QFile source(QString("%1/%2").arg(basedir).arg(filename));
  70. if(source.open(QIODevice::ReadOnly))
  71. {
  72. QDomDocument document;
  73. document.setContent(&source, true);
  74. QDomElement root = document.documentElement();
  75. context->name = root.attribute("id");
  76. QDomNodeList currentSet = document.elementsByTagName("button");
  77. QDomNode currentNode;
  78. QDomElement currentElement;
  79. for(int i = 0; i < currentSet.length(); i++)
  80. {
  81. currentNode = currentSet.at(i);
  82. currentElement = currentNode.toElement();
  83. addMessage(currentElement.attribute("name"), filename, currentNode.lineNumber());
  84. }
  85. currentSet = document.elementsByTagName("reporttitle");
  86. for(int i = 0; i < currentSet.length(); i++)
  87. {
  88. currentNode = currentSet.at(i);
  89. currentElement = currentNode.toElement();
  90. addMessage(currentElement.text(), filename, currentNode.lineNumber());
  91. }
  92. currentSet = document.elementsByTagName("menu");
  93. for(int i = 0; i < currentSet.length(); i++)
  94. {
  95. currentNode = currentSet.at(i);
  96. currentElement = currentNode.toElement();
  97. addMessage(currentElement.attribute("name"), filename, currentNode.lineNumber());
  98. if(currentElement.attribute("type") == "reports")
  99. {
  100. context->reportDirectory = currentElement.attribute("src");
  101. }
  102. }
  103. currentSet = document.elementsByTagName("item");
  104. for(int i = 0; i < currentSet.length(); i++)
  105. {
  106. currentNode = currentSet.at(i);
  107. currentElement = currentNode.toElement();
  108. addMessage(currentElement.text(), filename, currentNode.lineNumber());
  109. }
  110. currentSet = document.elementsByTagName("label");
  111. for(int i = 0; i < currentSet.length(); i++)
  112. {
  113. currentNode = currentSet.at(i);
  114. currentElement = currentNode.toElement();
  115. addMessage(currentElement.text(), filename, currentNode.lineNumber());
  116. }
  117. currentSet = document.elementsByTagName("decoration");
  118. for(int i = 0; i < currentSet.length(); i++)
  119. {
  120. currentNode = currentSet.at(i);
  121. currentElement = currentNode.toElement();
  122. addMessage(currentElement.attribute("name"), filename, currentNode.lineNumber());
  123. }
  124. currentSet = document.elementsByTagName("spinbox");
  125. for(int i = 0; i < currentSet.length(); i++)
  126. {
  127. currentNode = currentSet.at(i);
  128. currentElement = currentNode.toElement();
  129. if(currentElement.hasAttribute("pretext"))
  130. {
  131. addMessage(currentElement.attribute("pretext"), filename, currentNode.lineNumber());
  132. }
  133. if(currentElement.hasAttribute("posttext"))
  134. {
  135. addMessage(currentElement.attribute("posttext"), filename, currentNode.lineNumber());
  136. }
  137. }
  138. currentSet = document.elementsByTagName("measurementtable");
  139. for(int i = 0; i < currentSet.length(); i++)
  140. {
  141. currentNode = currentSet.at(i);
  142. currentElement = currentNode.toElement();
  143. if(currentElement.hasChildNodes())
  144. {
  145. for(int j = 0; j < currentElement.childNodes().count(); j++)
  146. {
  147. QDomNode childNode = currentElement.childNodes().at(j);
  148. if(childNode.isElement())
  149. {
  150. QDomElement childElement = childNode.toElement();
  151. if(childElement.tagName() == "column")
  152. {
  153. addMessage(childElement.text(), filename, childNode.lineNumber());
  154. }
  155. }
  156. }
  157. }
  158. }
  159. currentSet = document.elementsByTagName("sqltablearray");
  160. for(int i = 0; i < currentSet.length(); i++)
  161. {
  162. currentNode = currentSet.at(i);
  163. currentElement = currentNode.toElement();
  164. if(currentElement.hasChildNodes())
  165. {
  166. for(int j = 0; j < currentElement.childNodes().count(); j++)
  167. {
  168. QDomNode childNode = currentElement.childNodes().at(j);
  169. if(childNode.isElement())
  170. {
  171. QDomElement childElement = childNode.toElement();
  172. if(childElement.tagName() == "column")
  173. {
  174. addMessage(childElement.attribute("name"), filename, childNode.lineNumber());
  175. }
  176. }
  177. }
  178. }
  179. }
  180. currentSet = document.elementsByTagName("program");
  181. for(int i = 0; i < currentSet.length(); i++)
  182. {
  183. currentNode = currentSet.at(i);
  184. currentElement = currentNode.toElement();
  185. QString script = currentElement.text();
  186. QStringList scriptLines = script.split("\n");
  187. for(int j = 0; j < scriptLines.length(); j++)
  188. {
  189. if(scriptLines.at(j).contains("TTR"))
  190. {
  191. QString currentLine = scriptLines.at(j);
  192. currentLine = currentLine.remove(0, currentLine.indexOf("TTR"));
  193. currentLine = currentLine.remove(0, currentLine.indexOf("\"")+1);
  194. currentLine = currentLine.remove(0, currentLine.indexOf("\"")+1);
  195. currentLine = currentLine.remove(0, currentLine.indexOf("\"")+1);
  196. currentLine = currentLine.remove(currentLine.indexOf("\""), currentLine.length());
  197. addMessage(currentLine, filename, currentNode.lineNumber() + j + 1);
  198. }
  199. }
  200. }
  201. }
  202. source.close();
  203. return context;
  204. }
  205. // Produce a .ts file from a Typica configuration file.
  206. int generate(QString configfile, QString transfile)
  207. {
  208. QFile source(configfile);
  209. QFileInfo info(configfile);
  210. QDir directory = info.dir();
  211. if(!source.open(QIODevice::ReadOnly))
  212. {
  213. qDebug() << "Failed to open: " << configfile;
  214. return 1;
  215. }
  216. QDomDocument configuration;
  217. configuration.setContent(&source, true);
  218. QDomElement root = configuration.documentElement();
  219. QDomNodeList children = root.childNodes();
  220. QList<Context *> contexts;
  221. for(int i = 0; i < children.size(); i++)
  222. {
  223. QDomNode currentNode = children.at(i);
  224. QDomElement currentElement;
  225. if(currentNode.nodeName() == "include")
  226. {
  227. currentElement = currentNode.toElement();
  228. if(currentElement.hasAttribute("src"))
  229. {
  230. Context *context = buildContext(currentElement.attribute("src"), directory.path());
  231. if(context)
  232. {
  233. contexts.append(context);
  234. if(context->reportDirectory.length() > 0)
  235. {
  236. QDir reportsDirectory(QString("%1/%2").arg(directory.path()).arg(context->reportDirectory));
  237. reportsDirectory.setFilter(QDir::Files);
  238. reportsDirectory.setSorting(QDir::Name);
  239. QStringList nameFilter;
  240. nameFilter << "*.xml";
  241. reportsDirectory.setNameFilters(nameFilter);
  242. QStringList reportFiles = reportsDirectory.entryList();
  243. for(int j = 0; j < reportFiles.size(); j++)
  244. {
  245. Context *reportContext = buildContext(QString("%1/%2").arg(context->reportDirectory).arg(reportFiles.at(j)), directory.path());
  246. if(reportContext)
  247. {
  248. contexts.append(reportContext);
  249. }
  250. }
  251. }
  252. }
  253. }
  254. }
  255. }
  256. source.close();
  257. QFile ts(transfile);
  258. if(!ts.open(QIODevice::WriteOnly | QIODevice::Text))
  259. {
  260. qDebug() << "Failed to open output file: " << transfile;
  261. return 1;
  262. }
  263. QXmlStreamWriter xmlout(&ts);
  264. xmlout.setAutoFormatting(true);
  265. xmlout.writeStartDocument("1.0");
  266. xmlout.writeDTD("<!DOCTYPE TS>");
  267. xmlout.writeStartElement("TS");
  268. xmlout.writeAttribute("version", "2.0");
  269. QHashIterator<QString, Locations*> i(globalContext);
  270. xmlout.writeStartElement("context");
  271. xmlout.writeStartElement("name");
  272. xmlout.writeCharacters("configuration");
  273. xmlout.writeEndElement();
  274. while(i.hasNext())
  275. {
  276. i.next();
  277. xmlout.writeStartElement("message");
  278. xmlout.writeStartElement("source");
  279. xmlout.writeCharacters(i.key());
  280. xmlout.writeEndElement();
  281. for(int j = 0; j < i.value()->filenames.length(); j++)
  282. {
  283. xmlout.writeStartElement("location");
  284. xmlout.writeAttribute("filename", i.value()->filenames.at(j));
  285. xmlout.writeAttribute("line", i.value()->linenumbers.at(j));
  286. xmlout.writeEndElement();
  287. }
  288. // Uncomment to produce placeholder translations.
  289. /*
  290. xmlout.writeStartElement("translation");
  291. xmlout.writeCharacters("T" + i.key());
  292. xmlout.writeEndElement();
  293. */
  294. xmlout.writeEndElement();
  295. }
  296. xmlout.writeEndElement();
  297. xmlout.writeEndElement();
  298. xmlout.writeEndDocument();
  299. ts.close();
  300. return 0;
  301. }
  302. // Take the source and destination files from the command line arguments and
  303. // pass them to generate.
  304. int main(int argc, char **argv)
  305. {
  306. QCoreApplication app(argc, argv);
  307. QStringList arguments = app.arguments();
  308. if(arguments.length() != 3) {
  309. qDebug() << "Usage: ttsg configuration.xml translation.ts";
  310. return 1;
  311. }
  312. QString configfile = arguments.at(1);
  313. QString transfile = arguments.at(2);
  314. return generate(configfile, transfile);
  315. }