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