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