fix wxrc-generated c++ code to work in Unicode mode (patch #704064)
[wxWidgets.git] / utils / wxrc / wxrc.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: wxrc.cpp
3 // Purpose: XML resource compiler
4 // Author: Vaclav Slavik
5 // Created: 2000/03/05
6 // RCS-ID: $Id$
7 // Copyright: (c) 2000 Vaclav Slavik
8 // Licence: wxWindows licence
9 /////////////////////////////////////////////////////////////////////////////
10
11 #if defined(__GNUG__) && !defined(__APPLE__)
12 #pragma implementation
13 #pragma interface
14 #endif
15
16 // For compilers that support precompilation, includes "wx/wx.h".
17 #include "wx/wxprec.h"
18
19 #ifdef __BORLANDC__
20 #pragma hdrstop
21 #endif
22
23 // for all others, include the necessary headers (this file is usually all you
24 // need because it includes almost all "standard" wxWindows headers
25 #ifndef WX_PRECOMP
26 #include "wx/wx.h"
27 #endif
28
29 #include "wx/cmdline.h"
30 #include "wx/xrc/xml.h"
31 #include "wx/ffile.h"
32 #include "wx/filename.h"
33 #include "wx/wfstream.h"
34
35
36
37
38
39 /*
40 #if wxUSE_GUI
41 #error "You must compile the resource compiler with wxBase!"
42 #endif
43 */
44
45 class XmlResApp : public wxApp
46 {
47 public:
48
49 #if wxUSE_GUI
50 bool OnInit();
51 #else
52 virtual int OnRun();
53 #endif
54
55 private:
56
57 void ParseParams(const wxCmdLineParser& cmdline);
58 void CompileRes();
59 wxArrayString PrepareTempFiles();
60 void FindFilesInXML(wxXmlNode *node, wxArrayString& flist, const wxString& inputPath);
61
62 wxString GetInternalFileName(const wxString& name, const wxArrayString& flist);
63 void DeleteTempFiles(const wxArrayString& flist);
64 void MakePackageZIP(const wxArrayString& flist);
65 void MakePackageCPP(const wxArrayString& flist);
66 void MakePackagePython(const wxArrayString& flist);
67
68 void OutputGettext();
69 wxArrayString FindStrings();
70 wxArrayString FindStrings(wxXmlNode *node);
71
72 bool flagVerbose, flagCPP, flagPython, flagGettext;
73 wxString parOutput, parFuncname, parOutputPath;
74 wxArrayString parFiles;
75 int retCode;
76 };
77
78 IMPLEMENT_APP(XmlResApp)
79
80 #if wxUSE_GUI
81 bool XmlResApp::OnInit()
82 #else
83 int XmlResApp::OnRun()
84 #endif
85 {
86 static const wxCmdLineEntryDesc cmdLineDesc[] =
87 {
88 { wxCMD_LINE_SWITCH, _T("h"), _T("help"), _T("show help message"),
89 wxCMD_LINE_VAL_NONE, wxCMD_LINE_OPTION_HELP },
90 { wxCMD_LINE_SWITCH, _T("v"), _T("verbose"), _T("be verbose") },
91 { wxCMD_LINE_SWITCH, _T("c"), _T("cpp-code"), _T("output C++ source rather than .rsc file") },
92 { wxCMD_LINE_SWITCH, _T("p"), _T("python-code"), _T("output wxPython source rather than .rsc file") },
93 { wxCMD_LINE_SWITCH, _T("g"), _T("gettext"), _T("output list of translatable strings (to stdout or file if -o used)") },
94 { wxCMD_LINE_OPTION, _T("n"), _T("function"), _T("C++/Python function name (with -c or -p) [InitXmlResource]") },
95 { wxCMD_LINE_OPTION, _T("o"), _T("output"), _T("output file [resource.xrs/cpp]") },
96 #if 0 // not yet implemented
97 { wxCMD_LINE_OPTION, _T("l"), _T("list-of-handlers", _T("output list of neccessary handlers to this file" },
98 #endif
99 { wxCMD_LINE_PARAM, NULL, NULL, _T("input file(s)"),
100 wxCMD_LINE_VAL_STRING,
101 wxCMD_LINE_PARAM_MULTIPLE | wxCMD_LINE_OPTION_MANDATORY },
102
103 { wxCMD_LINE_NONE }
104 };
105
106 wxCmdLineParser parser(cmdLineDesc, argc, argv);
107
108 switch (parser.Parse())
109 {
110 case -1:
111 return 0;
112 break;
113
114 case 0:
115 retCode = 0;
116 ParseParams(parser);
117 if (flagGettext)
118 OutputGettext();
119 else
120 CompileRes();
121 #if wxUSE_GUI
122 return FALSE;
123 #else
124 return retCode;
125 #endif
126 break;
127
128 default:
129 #if wxUSE_GUI
130 return FALSE;
131 #else
132 return 1;
133 #endif
134 break;
135 }
136 }
137
138
139
140
141 void XmlResApp::ParseParams(const wxCmdLineParser& cmdline)
142 {
143 flagGettext = cmdline.Found(_T("g"));
144 flagVerbose = cmdline.Found(_T("v"));
145 flagCPP = cmdline.Found(_T("c"));
146 flagPython = cmdline.Found(_T("p"));
147
148 if (!cmdline.Found(_T("o"), &parOutput))
149 {
150 if (flagGettext)
151 parOutput = wxEmptyString;
152 else
153 {
154 if (flagCPP)
155 parOutput = _T("resource.cpp");
156 else if (flagPython)
157 parOutput = _T("resource.py");
158 else
159 parOutput = _T("resource.xrs");
160 }
161 }
162 wxFileName fn(parOutput);
163 fn.Normalize();
164 parOutput = fn.GetFullPath();
165 parOutputPath = wxPathOnly(parOutput);
166 if (!parOutputPath) parOutputPath = _T(".");
167
168 if (!cmdline.Found(_T("n"), &parFuncname))
169 parFuncname = _T("InitXmlResource");
170
171 for (size_t i = 0; i < cmdline.GetParamCount(); i++)
172 parFiles.Add(cmdline.GetParam(i));
173 }
174
175
176
177
178 void XmlResApp::CompileRes()
179 {
180 wxArrayString files = PrepareTempFiles();
181
182 wxRemoveFile(parOutput);
183
184 if (!retCode)
185 {
186 if (flagCPP)
187 MakePackageCPP(files);
188 else if (flagPython)
189 MakePackagePython(files);
190 else
191 MakePackageZIP(files);
192 }
193
194 DeleteTempFiles(files);
195 }
196
197
198 wxString XmlResApp::GetInternalFileName(const wxString& name, const wxArrayString& flist)
199 {
200 wxString name2 = name;
201 name2.Replace(_T(":"), _T("_"));
202 name2.Replace(_T("/"), _T("_"));
203 name2.Replace(_T("\\"), _T("_"));
204 name2.Replace(_T("*"), _T("_"));
205 name2.Replace(_T("?"), _T("_"));
206
207 wxString s = wxFileNameFromPath(parOutput) + _T("$") + name2;
208
209 if (wxFileExists(s) && flist.Index(s) == wxNOT_FOUND)
210 {
211 for (int i = 0;; i++)
212 {
213 s.Printf(wxFileNameFromPath(parOutput) + _T("$%03i-") + name2, i);
214 if (!wxFileExists(s) || flist.Index(s) != wxNOT_FOUND)
215 break;
216 }
217 }
218 return s;
219 }
220
221 wxArrayString XmlResApp::PrepareTempFiles()
222 {
223 wxArrayString flist;
224
225 for (size_t i = 0; i < parFiles.Count(); i++)
226 {
227 if (flagVerbose)
228 wxPrintf(_T("processing ") + parFiles[i] + _T("...\n"));
229
230 wxXmlDocument doc;
231
232 if (!doc.Load(parFiles[i]))
233 {
234 wxLogError(_T("Error parsing file ") + parFiles[i]);
235 retCode = 1;
236 continue;
237 }
238
239 wxString name, ext, path;
240 wxSplitPath(parFiles[i], &path, &name, &ext);
241
242 FindFilesInXML(doc.GetRoot(), flist, path);
243
244 wxString internalName = GetInternalFileName(parFiles[i], flist);
245
246 doc.Save(parOutputPath + wxFILE_SEP_PATH + internalName);
247 flist.Add(internalName);
248 }
249
250 return flist;
251 }
252
253
254 // Does 'node' contain filename information at all?
255 static bool NodeContainsFilename(wxXmlNode *node)
256 {
257 // Any bitmaps:
258 if (node->GetName() == _T("bitmap"))
259 return TRUE;
260
261 // URLs in wxHtmlWindow:
262 if (node->GetName() == _T("url"))
263 return TRUE;
264
265 // wxBitmapButton:
266 wxXmlNode *parent = node->GetParent();
267 if (parent != NULL &&
268 parent->GetPropVal(_T("class"), _T("")) == _T("wxBitmapButton") &&
269 (node->GetName() == _T("focus") ||
270 node->GetName() == _T("disabled") ||
271 node->GetName() == _T("selected")))
272 return TRUE;
273
274 // wxBitmap or wxIcon toplevel resources:
275 if (node->GetName() == _T("object"))
276 {
277 wxString klass = node->GetPropVal(_T("class"), wxEmptyString);
278 if (klass == _T("wxBitmap") || klass == _T("wxIcon"))
279 return TRUE;
280 }
281
282 return FALSE;
283 }
284
285 // find all files mentioned in structure, e.g. <bitmap>filename</bitmap>
286 void XmlResApp::FindFilesInXML(wxXmlNode *node, wxArrayString& flist, const wxString& inputPath)
287 {
288 // Is 'node' XML node element?
289 if (node == NULL) return;
290 if (node->GetType() != wxXML_ELEMENT_NODE) return;
291
292 bool containsFilename = NodeContainsFilename(node);
293
294 wxXmlNode *n = node->GetChildren();
295 while (n)
296 {
297 if (containsFilename &&
298 (n->GetType() == wxXML_TEXT_NODE ||
299 n->GetType() == wxXML_CDATA_SECTION_NODE))
300 {
301 wxString fullname;
302 if (wxIsAbsolutePath(n->GetContent()) || inputPath.empty())
303 fullname = n->GetContent();
304 else
305 fullname = inputPath + wxFILE_SEP_PATH + n->GetContent();
306
307 if (flagVerbose)
308 wxPrintf(_T("adding ") + fullname + _T("...\n"));
309
310 wxString filename = GetInternalFileName(n->GetContent(), flist);
311 n->SetContent(filename);
312
313 if (flist.Index(filename) == wxNOT_FOUND)
314 flist.Add(filename);
315
316 wxFileInputStream sin(fullname);
317 wxFileOutputStream sout(parOutputPath + wxFILE_SEP_PATH + filename);
318 sin.Read(sout); // copy the stream
319 }
320
321 // subnodes:
322 if (n->GetType() == wxXML_ELEMENT_NODE)
323 FindFilesInXML(n, flist, inputPath);
324
325 n = n->GetNext();
326 }
327 }
328
329
330
331 void XmlResApp::DeleteTempFiles(const wxArrayString& flist)
332 {
333 for (size_t i = 0; i < flist.Count(); i++)
334 wxRemoveFile(parOutputPath + wxFILE_SEP_PATH + flist[i]);
335 }
336
337
338
339 void XmlResApp::MakePackageZIP(const wxArrayString& flist)
340 {
341 wxString files;
342
343 for (size_t i = 0; i < flist.Count(); i++)
344 files += flist[i] + _T(" ");
345 files.RemoveLast();
346
347 if (flagVerbose)
348 wxPrintf(_T("compressing ") + parOutput + _T("...\n"));
349
350 wxString cwd = wxGetCwd();
351 wxSetWorkingDirectory(parOutputPath);
352 int execres = wxExecute(_T("zip -9 -j ") +
353 wxString(flagVerbose ? _T("") : _T("-q ")) +
354 parOutput + _T(" ") + files, TRUE);
355 wxSetWorkingDirectory(cwd);
356 if (execres == -1)
357 {
358 wxLogError(_T("Unable to execute zip program. Make sure it is in the path."));
359 wxLogError(_T("You can download it at http://www.cdrom.com/pub/infozip/"));
360 retCode = 1;
361 return;
362 }
363 }
364
365
366
367
368 static wxString FileToCppArray(wxString filename, int num)
369 {
370 wxString output;
371 wxString tmp;
372 wxString snum;
373 wxFFile file(filename, wxT("rb"));
374 size_t lng = file.Length();
375
376 snum.Printf(_T("%i"), num);
377 output.Printf(_T("static size_t xml_res_size_") + snum + _T(" = %i;\n"), lng);
378 output += _T("static unsigned char xml_res_file_") + snum + _T("[] = {\n");
379 // we cannot use string literals because MSVC is dumb wannabe compiler
380 // with arbitrary limitation to 2048 strings :(
381
382 unsigned char *buffer = new unsigned char[lng];
383 file.Read(buffer, lng);
384
385 for (size_t i = 0, linelng = 0; i < lng; i++)
386 {
387 tmp.Printf(_T("%i"), buffer[i]);
388 if (i != 0) output << _T(',');
389 if (linelng > 70)
390 {
391 linelng = 0;
392 output << _T("\n");
393 }
394 output << tmp;
395 linelng += tmp.Length()+1;
396 }
397
398 delete[] buffer;
399
400 output += _T("};\n\n");
401
402 return output;
403 }
404
405
406 void XmlResApp::MakePackageCPP(const wxArrayString& flist)
407 {
408 wxFFile file(parOutput, wxT("wt"));
409 size_t i;
410
411 if (flagVerbose)
412 wxPrintf(_T("creating C++ source file ") + parOutput + _T("...\n"));
413
414 file.Write(_T("")
415 _T("//\n")
416 _T("// This file was automatically generated by wxrc, do not edit by hand.\n")
417 _T("//\n\n")
418 _T("#include <wx/wxprec.h>\n")
419 _T("\n")
420 _T("#ifdef __BORLANDC__\n")
421 _T(" #pragma hdrstop\n")
422 _T("#endif\n")
423 _T("\n")
424 _T("#ifndef WX_PRECOMP\n")
425 _T(" #include <wx/wx.h>\n")
426 _T("#endif\n")
427 _T("")
428 _T("#include <wx/filesys.h>\n")
429 _T("#include <wx/fs_mem.h>\n")
430 _T("#include <wx/xrc/xmlres.h>\n")
431 _T("#include <wx/xrc/xh_all.h>\n")
432 _T("\n"));
433
434 for (i = 0; i < flist.Count(); i++)
435 file.Write(
436 FileToCppArray(parOutputPath + wxFILE_SEP_PATH + flist[i], i));
437
438 file.Write(_T("")
439 _T("void ") + parFuncname + wxT("()\n")
440 _T("{\n")
441 _T("\n")
442 _T(" // Check for memory FS. If not present, load the handler:\n")
443 _T(" {\n")
444 _T(" wxMemoryFSHandler::AddFile(wxT(\"XRC_resource/dummy_file\"), wxT(\"dummy one\"));\n")
445 _T(" wxFileSystem fsys;\n")
446 _T(" wxFSFile *f = fsys.OpenFile(wxT(\"memory:XRC_resource/dummy_file\"));\n")
447 _T(" wxMemoryFSHandler::RemoveFile(wxT(\"XRC_resource/dummy_file\"));\n")
448 _T(" if (f) delete f;\n")
449 _T(" else wxFileSystem::AddHandler(new wxMemoryFSHandler);\n")
450 _T(" }\n")
451 _T("\n"));
452
453 for (i = 0; i < flist.Count(); i++)
454 {
455 wxString s;
456 s.Printf(_T(" wxMemoryFSHandler::AddFile(wxT(\"XRC_resource/") + flist[i] +
457 _T("\"), xml_res_file_%i, xml_res_size_%i);\n"), i, i);
458 file.Write(s);
459 }
460
461 for (i = 0; i < parFiles.Count(); i++)
462 {
463 file.Write(_T(" wxXmlResource::Get()->Load(wxT(\"memory:XRC_resource/") +
464 GetInternalFileName(parFiles[i], flist) + _T("\"));\n"));
465 }
466
467 file.Write(_T("}\n"));
468
469
470 }
471
472 static wxString FileToPythonArray(wxString filename, int num)
473 {
474 wxString output;
475 wxString tmp;
476 wxString snum;
477 wxFFile file(filename, wxT("rb"));
478 size_t lng = file.Length();
479
480 snum.Printf(_T("%i"), num);
481 output = _T(" xml_res_file_") + snum + _T(" = \"\"\"\\\n");
482
483 unsigned char *buffer = new unsigned char[lng];
484 file.Read(buffer, lng);
485
486 for (size_t i = 0, linelng = 0; i < lng; i++)
487 {
488 unsigned char c = buffer[i];
489 if (c == '\n')
490 {
491 tmp = (wxChar)c;
492 linelng = 0;
493 }
494 else if (c < 32 || c > 127)
495 tmp.Printf(_T("\\x%02x"), c);
496 else if (c == '\\')
497 tmp = _T("\\\\");
498 else
499 tmp = (wxChar)c;
500 if (linelng > 70)
501 {
502 linelng = 0;
503 output << _T("\\\n");
504 }
505 output << tmp;
506 linelng += tmp.Length();
507 }
508
509 delete[] buffer;
510
511 output += _T("\"\"\"\n\n");
512
513 return output;
514 }
515
516
517 void XmlResApp::MakePackagePython(const wxArrayString& flist)
518 {
519 wxFFile file(parOutput, wxT("wt"));
520 size_t i;
521
522 if (flagVerbose)
523 wxPrintf(_T("creating Python source file ") + parOutput + _T("...\n"));
524
525 file.Write(
526 _T("#\n")
527 _T("# This file was automatically generated by wxrc, do not edit by hand.\n")
528 _T("#\n\n")
529 _T("from wxPython.wx import *\n")
530 _T("from wxPython.xrc import *\n\n")
531 );
532
533
534 file.Write(_T("def ") + parFuncname + _T("():\n"));
535
536 for (i = 0; i < flist.Count(); i++)
537 file.Write(
538 FileToPythonArray(parOutputPath + wxFILE_SEP_PATH + flist[i], i));
539
540 for (i = 0; i < flist.Count(); i++)
541 {
542 wxString s;
543 s.Printf(_T(" wxXmlResource_Get().LoadFromString(xml_res_file_%i)\n"), i);
544 file.Write(s);
545 }
546 }
547
548
549
550 void XmlResApp::OutputGettext()
551 {
552 wxArrayString str = FindStrings();
553
554 wxFFile fout;
555 if (!parOutput) fout.Attach(stdout);
556 else fout.Open(parOutput, wxT("wt"));
557
558 for (size_t i = 0; i < str.GetCount(); i++)
559 fout.Write(_T("_(\"") + str[i] + _T("\");\n"));
560
561 if (!parOutput) fout.Detach();
562 }
563
564
565
566 wxArrayString XmlResApp::FindStrings()
567 {
568 wxArrayString arr, a2;
569
570 for (size_t i = 0; i < parFiles.Count(); i++)
571 {
572 if (flagVerbose)
573 wxPrintf(_T("processing ") + parFiles[i] + _T("...\n"));
574
575 wxXmlDocument doc;
576 if (!doc.Load(parFiles[i]))
577 {
578 wxLogError(_T("Error parsing file ") + parFiles[i]);
579 retCode = 1;
580 continue;
581 }
582 a2 = FindStrings(doc.GetRoot());
583 WX_APPEND_ARRAY(arr, a2);
584 }
585
586 return arr;
587 }
588
589
590
591 static wxString ConvertText(const wxString& str)
592 {
593 wxString str2;
594 const wxChar *dt;
595
596 for (dt = str.c_str(); *dt; dt++)
597 {
598 if (*dt == wxT('_'))
599 {
600 if ( *(++dt) == wxT('_') )
601 str2 << wxT('_');
602 else
603 str2 << wxT('&') << *dt;
604 }
605 else
606 {
607 switch (*dt)
608 {
609 case wxT('\n') : str2 << wxT("\\n"); break;
610 case wxT('\t') : str2 << wxT("\\t"); break;
611 case wxT('\r') : str2 << wxT("\\r"); break;
612 case wxT('\\') : if ((*(dt+1) != 'n') &&
613 (*(dt+1) != 't') &&
614 (*(dt+1) != 'r'))
615 str2 << wxT("\\\\");
616 else
617 str2 << wxT("\\");
618 break;
619 case wxT('"') : str2 << wxT("\\\""); break;
620 default : str2 << *dt; break;
621 }
622 }
623 }
624
625 return str2;
626 }
627
628
629 wxArrayString XmlResApp::FindStrings(wxXmlNode *node)
630 {
631 wxArrayString arr;
632
633 wxXmlNode *n = node;
634 if (n == NULL) return arr;
635 n = n->GetChildren();
636
637 while (n)
638 {
639 if ((node->GetType() == wxXML_ELEMENT_NODE) &&
640 // parent is an element, i.e. has subnodes...
641 (n->GetType() == wxXML_TEXT_NODE ||
642 n->GetType() == wxXML_CDATA_SECTION_NODE) &&
643 // ...it is textnode...
644 (
645 node/*not n!*/->GetName() == _T("label") ||
646 (node/*not n!*/->GetName() == _T("value") &&
647 !n->GetContent().IsNumber()) ||
648 node/*not n!*/->GetName() == _T("help") ||
649 node/*not n!*/->GetName() == _T("longhelp") ||
650 node/*not n!*/->GetName() == _T("tooltip") ||
651 node/*not n!*/->GetName() == _T("htmlcode") ||
652 node/*not n!*/->GetName() == _T("title") ||
653 node/*not n!*/->GetName() == _T("item")
654 ))
655 // ...and known to contain translatable string
656 {
657 arr.Add(ConvertText(n->GetContent()));
658 }
659
660 // subnodes:
661 if (n->GetType() == wxXML_ELEMENT_NODE)
662 {
663 wxArrayString a2 = FindStrings(n);
664 WX_APPEND_ARRAY(arr, a2);
665 }
666
667 n = n->GetNext();
668 }
669 return arr;
670 }