1 /////////////////////////////////////////////////////////////////////////////
3 // Purpose: XML resource compiler
4 // Author: Vaclav Slavik
7 // Copyright: (c) 2000 Vaclav Slavik
8 // Licence: wxWindows licence
9 /////////////////////////////////////////////////////////////////////////////
11 #if defined(__GNUG__) && !defined(__APPLE__)
12 #pragma implementation
16 // For compilers that support precompilation, includes "wx/wx.h".
17 #include "wx/wxprec.h"
23 // for all others, include the necessary headers
29 #include "wx/cmdline.h"
30 #include "wx/xml/xml.h"
32 #include "wx/filename.h"
33 #include "wx/wfstream.h"
36 class XmlResApp
: public wxAppConsole
39 // don't use builtin cmd line parsing:
40 virtual bool OnInit() { return true; }
46 void ParseParams(const wxCmdLineParser
& cmdline
);
48 wxArrayString
PrepareTempFiles();
49 void FindFilesInXML(wxXmlNode
*node
, wxArrayString
& flist
, const wxString
& inputPath
);
51 wxString
GetInternalFileName(const wxString
& name
, const wxArrayString
& flist
);
52 void DeleteTempFiles(const wxArrayString
& flist
);
53 void MakePackageZIP(const wxArrayString
& flist
);
54 void MakePackageCPP(const wxArrayString
& flist
);
55 void MakePackagePython(const wxArrayString
& flist
);
58 wxArrayString
FindStrings();
59 wxArrayString
FindStrings(wxXmlNode
*node
);
61 bool flagVerbose
, flagCPP
, flagPython
, flagGettext
;
62 wxString parOutput
, parFuncname
, parOutputPath
;
63 wxArrayString parFiles
;
67 IMPLEMENT_APP_CONSOLE(XmlResApp
)
69 int XmlResApp::OnRun()
71 static const wxCmdLineEntryDesc cmdLineDesc
[] =
73 { wxCMD_LINE_SWITCH
, _T("h"), _T("help"), _T("show help message"),
74 wxCMD_LINE_VAL_NONE
, wxCMD_LINE_OPTION_HELP
},
75 { wxCMD_LINE_SWITCH
, _T("v"), _T("verbose"), _T("be verbose") },
76 { wxCMD_LINE_SWITCH
, _T("c"), _T("cpp-code"), _T("output C++ source rather than .rsc file") },
77 { wxCMD_LINE_SWITCH
, _T("p"), _T("python-code"), _T("output wxPython source rather than .rsc file") },
78 { wxCMD_LINE_SWITCH
, _T("g"), _T("gettext"), _T("output list of translatable strings (to stdout or file if -o used)") },
79 { wxCMD_LINE_OPTION
, _T("n"), _T("function"), _T("C++/Python function name (with -c or -p) [InitXmlResource]") },
80 { wxCMD_LINE_OPTION
, _T("o"), _T("output"), _T("output file [resource.xrs/cpp]") },
81 #if 0 // not yet implemented
82 { wxCMD_LINE_OPTION
, _T("l"), _T("list-of-handlers", _T("output list of neccessary handlers to this file" },
84 { wxCMD_LINE_PARAM
, NULL
, NULL
, _T("input file(s)"),
85 wxCMD_LINE_VAL_STRING
,
86 wxCMD_LINE_PARAM_MULTIPLE
| wxCMD_LINE_OPTION_MANDATORY
},
91 wxCmdLineParser
parser(cmdLineDesc
, argc
, argv
);
93 switch (parser
.Parse())
98 // break is unreachable because of return earlier
111 // break is unreachable because of return earlier
116 // default return moved outside of switch to avoid warning about lack of return in function
128 void XmlResApp::ParseParams(const wxCmdLineParser
& cmdline
)
130 flagGettext
= cmdline
.Found(_T("g"));
131 flagVerbose
= cmdline
.Found(_T("v"));
132 flagCPP
= cmdline
.Found(_T("c"));
133 flagPython
= cmdline
.Found(_T("p"));
135 if (!cmdline
.Found(_T("o"), &parOutput
))
138 parOutput
= wxEmptyString
;
142 parOutput
= _T("resource.cpp");
144 parOutput
= _T("resource.py");
146 parOutput
= _T("resource.xrs");
149 wxFileName
fn(parOutput
);
151 parOutput
= fn
.GetFullPath();
152 parOutputPath
= wxPathOnly(parOutput
);
153 if (!parOutputPath
) parOutputPath
= _T(".");
155 if (!cmdline
.Found(_T("n"), &parFuncname
))
156 parFuncname
= _T("InitXmlResource");
158 for (size_t i
= 0; i
< cmdline
.GetParamCount(); i
++)
161 wxString fn
=wxFindFirstFile(cmdline
.GetParam(i
), wxFILE
);
162 while (!fn
.IsEmpty())
168 parFiles
.Add(cmdline
.GetParam(i
));
176 void XmlResApp::CompileRes()
178 wxArrayString files
= PrepareTempFiles();
180 wxRemoveFile(parOutput
);
185 MakePackageCPP(files
);
187 MakePackagePython(files
);
189 MakePackageZIP(files
);
192 DeleteTempFiles(files
);
196 wxString
XmlResApp::GetInternalFileName(const wxString
& name
, const wxArrayString
& flist
)
198 wxString name2
= name
;
199 name2
.Replace(_T(":"), _T("_"));
200 name2
.Replace(_T("/"), _T("_"));
201 name2
.Replace(_T("\\"), _T("_"));
202 name2
.Replace(_T("*"), _T("_"));
203 name2
.Replace(_T("?"), _T("_"));
205 wxString s
= wxFileNameFromPath(parOutput
) + _T("$") + name2
;
207 if (wxFileExists(s
) && flist
.Index(s
) == wxNOT_FOUND
)
209 for (int i
= 0;; i
++)
211 s
.Printf(wxFileNameFromPath(parOutput
) + _T("$%03i-") + name2
, i
);
212 if (!wxFileExists(s
) || flist
.Index(s
) != wxNOT_FOUND
)
219 wxArrayString
XmlResApp::PrepareTempFiles()
223 for (size_t i
= 0; i
< parFiles
.Count(); i
++)
226 wxPrintf(_T("processing ") + parFiles
[i
] + _T("...\n"));
230 if (!doc
.Load(parFiles
[i
]))
232 wxLogError(_T("Error parsing file ") + parFiles
[i
]);
237 wxString name
, ext
, path
;
238 wxSplitPath(parFiles
[i
], &path
, &name
, &ext
);
240 FindFilesInXML(doc
.GetRoot(), flist
, path
);
242 wxString internalName
= GetInternalFileName(parFiles
[i
], flist
);
244 doc
.Save(parOutputPath
+ wxFILE_SEP_PATH
+ internalName
);
245 flist
.Add(internalName
);
252 // Does 'node' contain filename information at all?
253 static bool NodeContainsFilename(wxXmlNode
*node
)
256 if (node
->GetName() == _T("bitmap"))
259 // URLs in wxHtmlWindow:
260 if (node
->GetName() == _T("url"))
264 wxXmlNode
*parent
= node
->GetParent();
265 if (parent
!= NULL
&&
266 parent
->GetPropVal(_T("class"), _T("")) == _T("wxBitmapButton") &&
267 (node
->GetName() == _T("focus") ||
268 node
->GetName() == _T("disabled") ||
269 node
->GetName() == _T("selected")))
272 // wxBitmap or wxIcon toplevel resources:
273 if (node
->GetName() == _T("object"))
275 wxString klass
= node
->GetPropVal(_T("class"), wxEmptyString
);
276 if (klass
== _T("wxBitmap") || klass
== _T("wxIcon"))
283 // find all files mentioned in structure, e.g. <bitmap>filename</bitmap>
284 void XmlResApp::FindFilesInXML(wxXmlNode
*node
, wxArrayString
& flist
, const wxString
& inputPath
)
286 // Is 'node' XML node element?
287 if (node
== NULL
) return;
288 if (node
->GetType() != wxXML_ELEMENT_NODE
) return;
290 bool containsFilename
= NodeContainsFilename(node
);
292 wxXmlNode
*n
= node
->GetChildren();
295 if (containsFilename
&&
296 (n
->GetType() == wxXML_TEXT_NODE
||
297 n
->GetType() == wxXML_CDATA_SECTION_NODE
))
300 if (wxIsAbsolutePath(n
->GetContent()) || inputPath
.empty())
301 fullname
= n
->GetContent();
303 fullname
= inputPath
+ wxFILE_SEP_PATH
+ n
->GetContent();
306 wxPrintf(_T("adding ") + fullname
+ _T("...\n"));
308 wxString filename
= GetInternalFileName(n
->GetContent(), flist
);
309 n
->SetContent(filename
);
311 if (flist
.Index(filename
) == wxNOT_FOUND
)
314 wxFileInputStream
sin(fullname
);
315 wxFileOutputStream
sout(parOutputPath
+ wxFILE_SEP_PATH
+ filename
);
316 sin
.Read(sout
); // copy the stream
320 if (n
->GetType() == wxXML_ELEMENT_NODE
)
321 FindFilesInXML(n
, flist
, inputPath
);
329 void XmlResApp::DeleteTempFiles(const wxArrayString
& flist
)
331 for (size_t i
= 0; i
< flist
.Count(); i
++)
332 wxRemoveFile(parOutputPath
+ wxFILE_SEP_PATH
+ flist
[i
]);
337 void XmlResApp::MakePackageZIP(const wxArrayString
& flist
)
341 for (size_t i
= 0; i
< flist
.Count(); i
++)
342 files
+= flist
[i
] + _T(" ");
346 wxPrintf(_T("compressing ") + parOutput
+ _T("...\n"));
348 wxString cwd
= wxGetCwd();
349 wxSetWorkingDirectory(parOutputPath
);
350 int execres
= wxExecute(_T("zip -9 -j ") +
351 wxString(flagVerbose
? _T("") : _T("-q ")) +
352 parOutput
+ _T(" ") + files
, TRUE
);
353 wxSetWorkingDirectory(cwd
);
356 wxLogError(_T("Unable to execute zip program. Make sure it is in the path."));
357 wxLogError(_T("You can download it at http://www.cdrom.com/pub/infozip/"));
366 static wxString
FileToCppArray(wxString filename
, int num
)
371 wxFFile
file(filename
, wxT("rb"));
372 size_t lng
= file
.Length();
374 snum
.Printf(_T("%i"), num
);
375 output
.Printf(_T("static size_t xml_res_size_") + snum
+ _T(" = %i;\n"), lng
);
376 output
+= _T("static unsigned char xml_res_file_") + snum
+ _T("[] = {\n");
377 // we cannot use string literals because MSVC is dumb wannabe compiler
378 // with arbitrary limitation to 2048 strings :(
380 unsigned char *buffer
= new unsigned char[lng
];
381 file
.Read(buffer
, lng
);
383 for (size_t i
= 0, linelng
= 0; i
< lng
; i
++)
385 tmp
.Printf(_T("%i"), buffer
[i
]);
386 if (i
!= 0) output
<< _T(',');
393 linelng
+= tmp
.Length()+1;
398 output
+= _T("};\n\n");
404 void XmlResApp::MakePackageCPP(const wxArrayString
& flist
)
406 wxFFile
file(parOutput
, wxT("wt"));
410 wxPrintf(_T("creating C++ source file ") + parOutput
+ _T("...\n"));
414 _T("// This file was automatically generated by wxrc, do not edit by hand.\n")
416 _T("#include <wx/wxprec.h>\n")
418 _T("#ifdef __BORLANDC__\n")
419 _T(" #pragma hdrstop\n")
422 _T("#ifndef WX_PRECOMP\n")
423 _T(" #include <wx/wx.h>\n")
426 _T("#include <wx/filesys.h>\n")
427 _T("#include <wx/fs_mem.h>\n")
428 _T("#include <wx/xrc/xmlres.h>\n")
429 _T("#include <wx/xrc/xh_all.h>\n")
432 for (i
= 0; i
< flist
.Count(); i
++)
434 FileToCppArray(parOutputPath
+ wxFILE_SEP_PATH
+ flist
[i
], i
));
437 _T("void ") + parFuncname
+ wxT("()\n")
440 _T(" // Check for memory FS. If not present, load the handler:\n")
442 _T(" wxMemoryFSHandler::AddFile(wxT(\"XRC_resource/dummy_file\"), wxT(\"dummy one\"));\n")
443 _T(" wxFileSystem fsys;\n")
444 _T(" wxFSFile *f = fsys.OpenFile(wxT(\"memory:XRC_resource/dummy_file\"));\n")
445 _T(" wxMemoryFSHandler::RemoveFile(wxT(\"XRC_resource/dummy_file\"));\n")
446 _T(" if (f) delete f;\n")
447 _T(" else wxFileSystem::AddHandler(new wxMemoryFSHandler);\n")
451 for (i
= 0; i
< flist
.Count(); i
++)
454 s
.Printf(_T(" wxMemoryFSHandler::AddFile(wxT(\"XRC_resource/") + flist
[i
] +
455 _T("\"), xml_res_file_%i, xml_res_size_%i);\n"), i
, i
);
459 for (i
= 0; i
< parFiles
.Count(); i
++)
461 file
.Write(_T(" wxXmlResource::Get()->Load(wxT(\"memory:XRC_resource/") +
462 GetInternalFileName(parFiles
[i
], flist
) + _T("\"));\n"));
465 file
.Write(_T("}\n"));
470 static wxString
FileToPythonArray(wxString filename
, int num
)
475 wxFFile
file(filename
, wxT("rb"));
476 size_t lng
= file
.Length();
478 snum
.Printf(_T("%i"), num
);
479 output
= _T(" xml_res_file_") + snum
+ _T(" = \"\"\"\\\n");
481 unsigned char *buffer
= new unsigned char[lng
];
482 file
.Read(buffer
, lng
);
484 for (size_t i
= 0, linelng
= 0; i
< lng
; i
++)
486 unsigned char c
= buffer
[i
];
492 else if (c
< 32 || c
> 127)
493 tmp
.Printf(_T("\\x%02x"), c
);
501 output
<< _T("\\\n");
504 linelng
+= tmp
.Length();
509 output
+= _T("\"\"\"\n\n");
515 void XmlResApp::MakePackagePython(const wxArrayString
& flist
)
517 wxFFile
file(parOutput
, wxT("wt"));
521 wxPrintf(_T("creating Python source file ") + parOutput
+ _T("...\n"));
525 _T("# This file was automatically generated by wxrc, do not edit by hand.\n")
527 _T("from wxPython.wx import *\n")
528 _T("from wxPython.xrc import *\n\n")
532 file
.Write(_T("def ") + parFuncname
+ _T("():\n"));
534 for (i
= 0; i
< flist
.Count(); i
++)
536 FileToPythonArray(parOutputPath
+ wxFILE_SEP_PATH
+ flist
[i
], i
));
538 for (i
= 0; i
< flist
.Count(); i
++)
541 s
.Printf(_T(" wxXmlResource_Get().LoadFromString(xml_res_file_%i)\n"), i
);
548 void XmlResApp::OutputGettext()
550 wxArrayString str
= FindStrings();
553 if (!parOutput
) fout
.Attach(stdout
);
554 else fout
.Open(parOutput
, wxT("wt"));
556 for (size_t i
= 0; i
< str
.GetCount(); i
++)
557 fout
.Write(_T("_(\"") + str
[i
] + _T("\");\n"));
559 if (!parOutput
) fout
.Detach();
564 wxArrayString
XmlResApp::FindStrings()
566 wxArrayString arr
, a2
;
568 for (size_t i
= 0; i
< parFiles
.Count(); i
++)
571 wxPrintf(_T("processing ") + parFiles
[i
] + _T("...\n"));
574 if (!doc
.Load(parFiles
[i
]))
576 wxLogError(_T("Error parsing file ") + parFiles
[i
]);
580 a2
= FindStrings(doc
.GetRoot());
581 WX_APPEND_ARRAY(arr
, a2
);
589 static wxString
ConvertText(const wxString
& str
)
594 for (dt
= str
.c_str(); *dt
; dt
++)
598 if ( *(++dt
) == wxT('_') )
601 str2
<< wxT('&') << *dt
;
607 case wxT('\n') : str2
<< wxT("\\n"); break;
608 case wxT('\t') : str2
<< wxT("\\t"); break;
609 case wxT('\r') : str2
<< wxT("\\r"); break;
610 case wxT('\\') : if ((*(dt
+1) != 'n') &&
617 case wxT('"') : str2
<< wxT("\\\""); break;
618 default : str2
<< *dt
; break;
627 wxArrayString
XmlResApp::FindStrings(wxXmlNode
*node
)
632 if (n
== NULL
) return arr
;
633 n
= n
->GetChildren();
637 if ((node
->GetType() == wxXML_ELEMENT_NODE
) &&
638 // parent is an element, i.e. has subnodes...
639 (n
->GetType() == wxXML_TEXT_NODE
||
640 n
->GetType() == wxXML_CDATA_SECTION_NODE
) &&
641 // ...it is textnode...
643 node
/*not n!*/->GetName() == _T("label") ||
644 (node
/*not n!*/->GetName() == _T("value") &&
645 !n
->GetContent().IsNumber()) ||
646 node
/*not n!*/->GetName() == _T("help") ||
647 node
/*not n!*/->GetName() == _T("longhelp") ||
648 node
/*not n!*/->GetName() == _T("tooltip") ||
649 node
/*not n!*/->GetName() == _T("htmlcode") ||
650 node
/*not n!*/->GetName() == _T("title") ||
651 node
/*not n!*/->GetName() == _T("item")
653 // ...and known to contain translatable string
655 arr
.Add(ConvertText(n
->GetContent()));
659 if (n
->GetType() == wxXML_ELEMENT_NODE
)
661 wxArrayString a2
= FindStrings(n
);
662 WX_APPEND_ARRAY(arr
, a2
);