]>
Commit | Line | Data |
---|---|---|
1 | #---------------------------------------------------------------------- | |
2 | # Name: wx.tools.pywxrc | |
3 | # Purpose: XML resource compiler | |
4 | # | |
5 | # Author: Robin Dunn | |
6 | # Based on wxrc.cpp by Vaclav Slavik, Eduardo Marques | |
7 | # Ported to Python in order to not require yet another | |
8 | # binary in wxPython distributions | |
9 | # | |
10 | # Massive rework by Eli Golovinsky | |
11 | # | |
12 | # RCS-ID: $Id$ | |
13 | # Copyright: (c) 2004 by Total Control Software, 2000 Vaclav Slavik | |
14 | # Licence: wxWindows license | |
15 | #---------------------------------------------------------------------- | |
16 | ||
17 | """ | |
18 | pywxrc -- Python XML resource compiler | |
19 | (see http://wiki.wxpython.org/index.cgi/pywxrc for more info) | |
20 | ||
21 | Usage: python pywxrc.py -h | |
22 | python pywxrc.py [-p] [-g] [-e] [-o filename] xrc input files... | |
23 | ||
24 | -h, --help show help message | |
25 | -p, --python generate python module | |
26 | -g, --gettext output list of translatable strings (may be combined with -p) | |
27 | -e, --embed embed XRC resources in the output file | |
28 | -o, --output output filename, or - for stdout | |
29 | """ | |
30 | ||
31 | import sys, os, getopt, glob, re | |
32 | import xml.dom.minidom as minidom | |
33 | import wx | |
34 | import wx.xrc | |
35 | ||
36 | #---------------------------------------------------------------------- | |
37 | ||
38 | class PythonTemplates: | |
39 | FILE_HEADER = """\ | |
40 | # This file was automatically generated by pywxrc, do not edit by hand. | |
41 | # -*- coding: UTF-8 -*- | |
42 | ||
43 | import wx | |
44 | import wx.xrc as xrc | |
45 | ||
46 | __res = None | |
47 | ||
48 | def get_resources(): | |
49 | \"\"\" This function provides access to the XML resources in this module.\"\"\" | |
50 | global __res | |
51 | if __res == None: | |
52 | __init_resources() | |
53 | return __res | |
54 | ||
55 | """ | |
56 | ||
57 | CLASS_HEADER = """\ | |
58 | class xrc%(windowName)s(wx.%(windowClass)s): | |
59 | def PreCreate(self): | |
60 | \"\"\" This function is called during the class's initialization. | |
61 | ||
62 | Override it for custom setup before the window is created usually to | |
63 | set additional window styles using SetWindowStyle() and SetExtraStyle().\"\"\" | |
64 | pass | |
65 | ||
66 | def __init__(self, parent): | |
67 | # Two stage creation (see http://wiki.wxpython.org/index.cgi/TwoStageCreation) | |
68 | pre = wx.Pre%(windowClass)s() | |
69 | get_resources().LoadOn%(windowClass)s(pre, parent, "%(windowName)s") | |
70 | self.PreCreate() | |
71 | self.PostCreate(pre) | |
72 | ||
73 | # Define variables for the controls | |
74 | """ | |
75 | ||
76 | CREATE_WIDGET_VAR = """\ | |
77 | self.%(widgetName)s = xrc.XRCCTRL(self, \"%(widgetName)s\") | |
78 | """ | |
79 | ||
80 | INIT_RESOURE_HEADER = """\ | |
81 | # ------------------------ Resource data ---------------------- | |
82 | ||
83 | def __init_resources(): | |
84 | global __res | |
85 | __res = xrc.EmptyXmlResource() | |
86 | """ | |
87 | ||
88 | LOAD_RES_FILE = """\ | |
89 | __res.Load('%(resourceFilename)s')""" | |
90 | ||
91 | FILE_AS_STRING = """\ | |
92 | %(filename)s = '''\\ | |
93 | %(fileData)s''' | |
94 | ||
95 | """ | |
96 | ||
97 | PREPARE_MEMFS = """\ | |
98 | wx.FileSystem.AddHandler(wx.MemoryFSHandler()) | |
99 | """ | |
100 | ||
101 | ADD_FILE_TO_MEMFS = """\ | |
102 | wx.MemoryFSHandler.AddFile('XRC/%(memoryPath)s/%(filename)s', %(filename)s) | |
103 | """ | |
104 | ||
105 | LOAD_RES_MEMFS = """\ | |
106 | __res.Load('memory:XRC/%(memoryPath)s/%(resourceFilename)s') | |
107 | """ | |
108 | ||
109 | GETTEXT_DUMMY_FUNC = """ | |
110 | # ----------------------- Gettext strings --------------------- | |
111 | ||
112 | def __gettext_strings(): | |
113 | # This is a dummy function that lists all the strings that are used in | |
114 | # the XRC file in the _("a string") format to be recognized by GNU | |
115 | # gettext utilities (specificaly the xgettext utility) and the | |
116 | # mki18n.py script. For more information see: | |
117 | # http://wiki.wxpython.org/index.cgi/Internationalization | |
118 | ||
119 | def _(str): pass | |
120 | ||
121 | %s | |
122 | """ | |
123 | ||
124 | #---------------------------------------------------------------------- | |
125 | ||
126 | class XmlResourceCompiler: | |
127 | ||
128 | templates = PythonTemplates() | |
129 | ||
130 | """This class generates Python code from XML resource files (XRC).""" | |
131 | ||
132 | def MakePythonModule(self, inputFiles, outputFilename, | |
133 | embedResources=False, generateGetText=False): | |
134 | ||
135 | outputFile = self._OpenOutputFile(outputFilename) | |
136 | ||
137 | classes = [] | |
138 | resources = [] | |
139 | gettextStrings = [] | |
140 | ||
141 | # process all the inputFiles, collecting the output data | |
142 | for inFile in inputFiles: | |
143 | resourceDocument = minidom.parse(inFile) | |
144 | classes.append(self.GenerateClasses(resourceDocument)) | |
145 | ||
146 | if embedResources: | |
147 | res = self.GenerateInitResourcesEmbedded(inFile, resourceDocument) | |
148 | else: | |
149 | res = self.GenerateInitResourcesFile(inFile, resourceDocument) | |
150 | resources.append(res) | |
151 | ||
152 | if generateGetText: | |
153 | gettextStrings += self.FindStringsInNode(resourceDocument.firstChild) | |
154 | ||
155 | # now write it all out | |
156 | print >>outputFile, self.templates.FILE_HEADER | |
157 | ||
158 | # Note: Technically it is not legal to have anything other | |
159 | # than ascii for class and variable names, but since the user | |
160 | # can create the XML with non-ascii names we'll go ahead and | |
161 | # allow for it here, and then let Python complain about it | |
162 | # later when they try to run the program. | |
163 | classes = u"\n".join(classes) | |
164 | print >>outputFile, classes.encode("UTF-8") | |
165 | ||
166 | print >>outputFile, self.templates.INIT_RESOURE_HEADER | |
167 | if embedResources: | |
168 | print >>outputFile, self.templates.PREPARE_MEMFS | |
169 | resources = u"\n".join(resources) | |
170 | print >>outputFile, resources.encode("UTF-8") | |
171 | ||
172 | if generateGetText: | |
173 | # These have already been converted to utf-8... | |
174 | gettextStrings = [' _("%s")' % s for s in gettextStrings] | |
175 | gettextStrings = "\n".join(gettextStrings) | |
176 | print >>outputFile, self.templates.GETTEXT_DUMMY_FUNC % gettextStrings | |
177 | ||
178 | #------------------------------------------------------------------- | |
179 | ||
180 | def MakeGetTextOutput(self, inputFiles, outputFilename): | |
181 | """ | |
182 | Just output the gettext strings by themselves, with no other | |
183 | code generation. | |
184 | """ | |
185 | outputFile = self._OpenOutputFile(outputFilename) | |
186 | for inFile in inputFiles: | |
187 | resourceDocument = minidom.parse(inFile) | |
188 | resource = resourceDocument.firstChild | |
189 | strings = self.FindStringsInNode(resource) | |
190 | strings = ['_("%s");' % s for s in strings] | |
191 | print >>outputFile, "\n".join(strings) | |
192 | ||
193 | #------------------------------------------------------------------- | |
194 | ||
195 | def GenerateClasses(self, resourceDocument): | |
196 | outputList = [] | |
197 | ||
198 | resource = resourceDocument.firstChild | |
199 | topWindows = [e for e in resource.childNodes | |
200 | if e.nodeType == e.ELEMENT_NODE and e.tagName == "object"] | |
201 | ||
202 | # Generate a class for each top-window object (Frame, Panel, Dialog, etc.) | |
203 | for topWindow in topWindows: | |
204 | windowClass = topWindow.getAttribute("class") | |
205 | windowClass = re.sub("^wx", "", windowClass) | |
206 | windowName = topWindow.getAttribute("name") | |
207 | outputList.append(self.templates.CLASS_HEADER % locals()) | |
208 | ||
209 | # Generate a variable for each control, and standard event handlers | |
210 | # for standard controls. | |
211 | for widget in topWindow.getElementsByTagName("object"): | |
212 | widgetClass = widget.getAttribute("class") | |
213 | widgetClass = re.sub("^wx", "", widgetClass) | |
214 | widgetName = widget.getAttribute("name") | |
215 | if (widgetName != "" and widgetClass != "" and | |
216 | widgetClass not in | |
217 | ['tool', 'unknown', 'notebookpage', | |
218 | 'separator', 'sizeritem', 'MenuItem']): | |
219 | outputList.append(self.templates.CREATE_WIDGET_VAR % locals()) | |
220 | outputList.append('\n\n') | |
221 | ||
222 | return "".join(outputList) | |
223 | ||
224 | #------------------------------------------------------------------- | |
225 | ||
226 | def GenerateInitResourcesEmbedded(self, resourceFilename, resourceDocument): | |
227 | outputList = [] | |
228 | files = [] | |
229 | ||
230 | resourcePath = os.path.split(resourceFilename)[0] | |
231 | memoryPath = self.GetMemoryFilename(os.path.splitext(os.path.split(resourceFilename)[1])[0]) | |
232 | resourceFilename = self.GetMemoryFilename(os.path.split(resourceFilename)[1]) | |
233 | ||
234 | self.ReplaceFilenamesInXRC(resourceDocument.firstChild, files, resourcePath) | |
235 | ||
236 | filename = resourceFilename | |
237 | fileData = resourceDocument.toxml() | |
238 | outputList.append(self.templates.FILE_AS_STRING % locals()) | |
239 | ||
240 | for f in files: | |
241 | filename = self.GetMemoryFilename(f) | |
242 | fileData = self.FileToString(os.path.join(resourcePath, f)) | |
243 | outputList.append(self.templates.FILE_AS_STRING % locals()) | |
244 | ||
245 | for f in [resourceFilename] + files: | |
246 | filename = self.GetMemoryFilename(f) | |
247 | outputList.append(self.templates.ADD_FILE_TO_MEMFS % locals()) | |
248 | ||
249 | outputList.append(self.templates.LOAD_RES_MEMFS % locals()) | |
250 | ||
251 | return "".join(outputList) | |
252 | ||
253 | #------------------------------------------------------------------- | |
254 | ||
255 | def GenerateInitResourcesFile(self, resourceFilename, resourceDocument): | |
256 | # take only the filename portion out of resourceFilename | |
257 | resourceFilename = os.path.split(resourceFilename)[1] | |
258 | outputList = [] | |
259 | outputList.append(self.templates.LOAD_RES_FILE % locals()) | |
260 | return "".join(outputList) | |
261 | ||
262 | #------------------------------------------------------------------- | |
263 | ||
264 | def GetMemoryFilename(self, filename): | |
265 | # Remove special chars from the filename | |
266 | return re.sub(r"[^A-Za-z0-9_]", "_", filename) | |
267 | ||
268 | #------------------------------------------------------------------- | |
269 | ||
270 | def FileToString(self, filename): | |
271 | outputList = [] | |
272 | ||
273 | buffer = open(filename, "rb").read() | |
274 | fileLen = len(buffer) | |
275 | ||
276 | linelng = 0 | |
277 | for i in xrange(fileLen): | |
278 | s = buffer[i] | |
279 | c = ord(s) | |
280 | if s == '\n': | |
281 | tmp = s | |
282 | linelng = 0 | |
283 | elif c < 32 or c > 127 or s == "'": | |
284 | tmp = "\\x%02x" % c | |
285 | elif s == "\\": | |
286 | tmp = "\\\\" | |
287 | else: | |
288 | tmp = s | |
289 | ||
290 | if linelng > 70: | |
291 | linelng = 0 | |
292 | outputList.append("\\\n") | |
293 | ||
294 | outputList.append(tmp) | |
295 | linelng += len(tmp) | |
296 | ||
297 | return "".join(outputList) | |
298 | ||
299 | #------------------------------------------------------------------- | |
300 | ||
301 | def NodeContainsFilename(self, node): | |
302 | """ Does 'node' contain filename information at all? """ | |
303 | ||
304 | # Any bitmaps: | |
305 | if node.nodeName == "bitmap": | |
306 | return True | |
307 | ||
308 | if node.nodeName == "icon": | |
309 | return True | |
310 | ||
311 | # URLs in wxHtmlWindow: | |
312 | if node.nodeName == "url": | |
313 | return True | |
314 | ||
315 | # wxBitmapButton: | |
316 | parent = node.parentNode | |
317 | if parent.__class__ != minidom.Document and \ | |
318 | parent.getAttribute("class") == "wxBitmapButton" and \ | |
319 | (node.nodeName == "focus" or node.nodeName == "disabled" or | |
320 | node.nodeName == "selected"): | |
321 | return True | |
322 | ||
323 | # wxBitmap or wxIcon toplevel resources: | |
324 | if node.nodeName == "object": | |
325 | klass = node.getAttribute("class") | |
326 | if klass == "wxBitmap" or klass == "wxIcon": | |
327 | return True | |
328 | ||
329 | return False | |
330 | ||
331 | #------------------------------------------------------------------- | |
332 | ||
333 | def ReplaceFilenamesInXRC(self, node, files, resourcePath): | |
334 | """ Finds all files mentioned in resource file, e.g. <bitmap>filename</bitmap> | |
335 | and replaces them with the memory filenames. | |
336 | ||
337 | Fills a list of the filenames found.""" | |
338 | ||
339 | # Is 'node' XML node element? | |
340 | if node is None: return | |
341 | if node.nodeType != minidom.Document.ELEMENT_NODE: return | |
342 | ||
343 | containsFilename = self.NodeContainsFilename(node); | |
344 | ||
345 | for n in node.childNodes: | |
346 | ||
347 | if (containsFilename and | |
348 | (n.nodeType == minidom.Document.TEXT_NODE or | |
349 | n.nodeType == minidom.Document.CDATA_SECTION_NODE)): | |
350 | ||
351 | filename = n.nodeValue | |
352 | memoryFilename = self.GetMemoryFilename(filename) | |
353 | n.nodeValue = memoryFilename | |
354 | ||
355 | if filename not in files: | |
356 | files.append(filename) | |
357 | ||
358 | # Recurse into children | |
359 | if n.nodeType == minidom.Document.ELEMENT_NODE: | |
360 | self.ReplaceFilenamesInXRC(n, files, resourcePath); | |
361 | ||
362 | #------------------------------------------------------------------- | |
363 | ||
364 | def FindStringsInNode(self, parent): | |
365 | def is_number(st): | |
366 | try: | |
367 | i = int(st) | |
368 | return True | |
369 | except ValueError: | |
370 | return False | |
371 | ||
372 | strings = [] | |
373 | if parent is None: | |
374 | return strings; | |
375 | ||
376 | for child in parent.childNodes: | |
377 | if ((parent.nodeType == parent.ELEMENT_NODE) and | |
378 | # parent is an element, i.e. has subnodes... | |
379 | (child.nodeType == child.TEXT_NODE or | |
380 | child.nodeType == child.CDATA_SECTION_NODE) and | |
381 | # ...it is textnode... | |
382 | ( | |
383 | parent.tagName == "label" or | |
384 | (parent.tagName == "value" and | |
385 | not is_number(child.nodeValue)) or | |
386 | parent.tagName == "help" or | |
387 | parent.tagName == "longhelp" or | |
388 | parent.tagName == "tooltip" or | |
389 | parent.tagName == "htmlcode" or | |
390 | parent.tagName == "title" or | |
391 | parent.tagName == "item" | |
392 | )): | |
393 | # ...and known to contain translatable string | |
394 | if (parent.getAttribute("translate") != "0"): | |
395 | strings.append(self.ConvertText(child.nodeValue)) | |
396 | ||
397 | # subnodes: | |
398 | if child.nodeType == child.ELEMENT_NODE: | |
399 | strings += self.FindStringsInNode(child) | |
400 | ||
401 | return strings | |
402 | ||
403 | #------------------------------------------------------------------- | |
404 | ||
405 | def ConvertText(self, st): | |
406 | st2 = "" | |
407 | dt = list(st) | |
408 | ||
409 | skipNext = False | |
410 | for i in range(len(dt)): | |
411 | if skipNext: | |
412 | skipNext = False | |
413 | continue | |
414 | ||
415 | if dt[i] == '_': | |
416 | if dt[i+1] == '_': | |
417 | st2 += '_' | |
418 | skipNext = True | |
419 | else: | |
420 | st2 += '&' | |
421 | elif dt[i] == '\n': | |
422 | st2 += '\\n' | |
423 | elif dt[i] == '\t': | |
424 | st2 += '\\t' | |
425 | elif dt[i] == '\r': | |
426 | st2 += '\\r' | |
427 | elif dt[i] == '\\': | |
428 | if dt[i+1] not in ['n', 't', 'r']: | |
429 | st2 += '\\\\' | |
430 | else: | |
431 | st2 += '\\' | |
432 | elif dt[i] == '"': | |
433 | st2 += '\\"' | |
434 | else: | |
435 | st2 += dt[i] | |
436 | ||
437 | return st2.encode("UTF-8") | |
438 | ||
439 | ||
440 | #------------------------------------------------------------------- | |
441 | ||
442 | def _OpenOutputFile(self, outputFilename): | |
443 | if outputFilename == "-": | |
444 | outputFile = sys.stdout | |
445 | else: | |
446 | try: | |
447 | outputFile = open(outputFilename, "wt") | |
448 | except IOError: | |
449 | raise IOError("Can't write output to '%s'" % outputFilename) | |
450 | return outputFile | |
451 | ||
452 | ||
453 | ||
454 | ||
455 | ||
456 | #--------------------------------------------------------------------------- | |
457 | ||
458 | def main(args): | |
459 | resourceFilename = "" | |
460 | outputFilename = None | |
461 | embedResources = False | |
462 | generateGetText = False | |
463 | generatePython = False | |
464 | ||
465 | try: | |
466 | opts, args = getopt.gnu_getopt(args, | |
467 | "hpgeo:", | |
468 | "help python gettext embed output=".split()) | |
469 | except getopt.GetoptError, e: | |
470 | print "\nError : %s\n" % str(e) | |
471 | print __doc__ | |
472 | sys.exit(1) | |
473 | ||
474 | # If there is no input file argument, show help and exit | |
475 | if not args: | |
476 | print __doc__ | |
477 | print "No xrc input file was specified." | |
478 | sys.exit(1) | |
479 | ||
480 | # Parse options and arguments | |
481 | for opt, val in opts: | |
482 | if opt in ["-h", "--help"]: | |
483 | print __doc__ | |
484 | sys.exit(1) | |
485 | ||
486 | if opt in ["-p", "--python"]: | |
487 | generatePython = True | |
488 | ||
489 | if opt in ["-o", "--output"]: | |
490 | outputFilename = val | |
491 | ||
492 | if opt in ["-e", "--embed"]: | |
493 | embedResources = True | |
494 | ||
495 | if opt in ["-g", "--gettext"]: | |
496 | generateGetText = True | |
497 | ||
498 | ||
499 | # check for and expand any wildcards in the list of input files | |
500 | inputFiles = [] | |
501 | for arg in args: | |
502 | inputFiles += glob.glob(arg) | |
503 | ||
504 | ||
505 | comp = XmlResourceCompiler() | |
506 | ||
507 | try: | |
508 | if generatePython: | |
509 | if not outputFilename: | |
510 | outputFilename = os.path.splitext(args[0])[0] + "_xrc.py" | |
511 | comp.MakePythonModule(inputFiles, outputFilename, | |
512 | embedResources, generateGetText) | |
513 | ||
514 | elif generateGetText: | |
515 | if not outputFilename: | |
516 | outputFilename = '-' | |
517 | comp.MakeGetTextOutput(inputFiles, outputFilename) | |
518 | ||
519 | else: | |
520 | print __doc__ | |
521 | print "One or both of -p, -g must be specified." | |
522 | sys.exit(1) | |
523 | ||
524 | ||
525 | except IOError, e: | |
526 | print >>sys.stderr, "%s." % str(e) | |
527 | else: | |
528 | if outputFilename != "-": | |
529 | print >>sys.stderr, "Resources written to %s." % outputFilename | |
530 | ||
531 | if __name__ == "__main__": | |
532 | main(sys.argv[1:]) | |
533 |