]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/tools/pywxrc.py
documented __WXDFB__ and __WXGTK2n__
[wxWidgets.git] / wxPython / wx / tools / pywxrc.py
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, pre):
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 self.PreCreate(pre)
70 get_resources().LoadOn%(windowClass)s(pre, parent, "%(windowName)s")
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 MENU_CLASS_HEADER = """\
81 class xrc%(windowName)s(wx.%(windowClass)s):
82 def __init__(self):
83 pre = get_resources().LoadMenu("%(windowName)s")
84
85 # This is a copy of Robin's PostCreate voodoo magic in wx.Window that
86 # relinks the self object with the menu object.
87 self.this = pre.this
88 self.thisown = pre.thisown
89 pre.thisown = 0
90 if hasattr(self, '_setOORInfo'):
91 self._setOORInfo(self)
92
93 # Define variables for the menu items
94 """
95
96 MENUBAR_CLASS_HEADER = """\
97 class xrc%(windowName)s(wx.%(windowClass)s):
98 def __init__(self):
99 pre = get_resources().LoadMenuBar("%(windowName)s")
100 self.PostCreate(pre)
101
102 # Define variables for the menu items
103 """
104
105 CREATE_MENUITEM_VAR = """\
106 self.%(widgetName)s = self.FindItemById(xrc.XRCID(\"%(widgetName)s\"))
107 """
108
109 INIT_RESOURE_HEADER = """\
110 # ------------------------ Resource data ----------------------
111
112 def __init_resources():
113 global __res
114 __res = xrc.EmptyXmlResource()
115 """
116
117 LOAD_RES_FILE = """\
118 __res.Load('%(resourceFilename)s')"""
119
120 FILE_AS_STRING = """\
121 %(filename)s = '''\\
122 %(fileData)s'''
123
124 """
125
126 PREPARE_MEMFS = """\
127 wx.FileSystem.AddHandler(wx.MemoryFSHandler())
128 """
129
130 ADD_FILE_TO_MEMFS = """\
131 wx.MemoryFSHandler.AddFile('XRC/%(memoryPath)s/%(filename)s', %(filename)s)
132 """
133
134 LOAD_RES_MEMFS = """\
135 __res.Load('memory:XRC/%(memoryPath)s/%(resourceFilename)s')
136 """
137
138 GETTEXT_DUMMY_FUNC = """
139 # ----------------------- Gettext strings ---------------------
140
141 def __gettext_strings():
142 # This is a dummy function that lists all the strings that are used in
143 # the XRC file in the _("a string") format to be recognized by GNU
144 # gettext utilities (specificaly the xgettext utility) and the
145 # mki18n.py script. For more information see:
146 # http://wiki.wxpython.org/index.cgi/Internationalization
147
148 def _(str): pass
149
150 %s
151 """
152
153 #----------------------------------------------------------------------
154
155 class XmlResourceCompiler:
156
157 templates = PythonTemplates()
158
159 """This class generates Python code from XML resource files (XRC)."""
160
161 def MakePythonModule(self, inputFiles, outputFilename,
162 embedResources=False, generateGetText=False):
163
164 outputFile = self._OpenOutputFile(outputFilename)
165
166 classes = []
167 resources = []
168 gettextStrings = []
169
170 # process all the inputFiles, collecting the output data
171 for inFile in inputFiles:
172 resourceDocument = minidom.parse(inFile)
173 classes.append(self.GenerateClasses(resourceDocument))
174
175 if embedResources:
176 res = self.GenerateInitResourcesEmbedded(inFile, resourceDocument)
177 else:
178 res = self.GenerateInitResourcesFile(inFile, resourceDocument)
179 resources.append(res)
180
181 if generateGetText:
182 gettextStrings += self.FindStringsInNode(resourceDocument.firstChild)
183
184 # now write it all out
185 print >>outputFile, self.templates.FILE_HEADER
186
187 # Note: Technically it is not legal to have anything other
188 # than ascii for class and variable names, but since the user
189 # can create the XML with non-ascii names we'll go ahead and
190 # allow for it here, and then let Python complain about it
191 # later when they try to run the program.
192 classes = u"\n".join(classes)
193 print >>outputFile, classes.encode("UTF-8")
194
195 print >>outputFile, self.templates.INIT_RESOURE_HEADER
196 if embedResources:
197 print >>outputFile, self.templates.PREPARE_MEMFS
198 resources = u"\n".join(resources)
199 print >>outputFile, resources.encode("UTF-8")
200
201 if generateGetText:
202 # These have already been converted to utf-8...
203 gettextStrings = [' _("%s")' % s for s in gettextStrings]
204 gettextStrings = "\n".join(gettextStrings)
205 print >>outputFile, self.templates.GETTEXT_DUMMY_FUNC % gettextStrings
206
207 #-------------------------------------------------------------------
208
209 def MakeGetTextOutput(self, inputFiles, outputFilename):
210 """
211 Just output the gettext strings by themselves, with no other
212 code generation.
213 """
214 outputFile = self._OpenOutputFile(outputFilename)
215 for inFile in inputFiles:
216 resourceDocument = minidom.parse(inFile)
217 resource = resourceDocument.firstChild
218 strings = self.FindStringsInNode(resource)
219 strings = ['_("%s");' % s for s in strings]
220 print >>outputFile, "\n".join(strings)
221
222 #-------------------------------------------------------------------
223
224 def GenerateClasses(self, resourceDocument):
225 outputList = []
226
227 resource = resourceDocument.firstChild
228 topWindows = [e for e in resource.childNodes
229 if e.nodeType == e.ELEMENT_NODE and e.tagName == "object"]
230
231 # Generate a class for each top-window object (Frame, Panel, Dialog, etc.)
232 for topWindow in topWindows:
233 windowClass = topWindow.getAttribute("class")
234 windowClass = re.sub("^wx", "", windowClass)
235 windowName = topWindow.getAttribute("name")
236
237 if windowClass in ["MenuBar"]:
238 outputList.append(self.templates.MENUBAR_CLASS_HEADER % locals())
239 elif windowClass in ["Menu"]:
240 outputList.append(self.templates.MENU_CLASS_HEADER % locals())
241 else:
242 outputList.append(self.templates.CLASS_HEADER % locals())
243
244 # Generate a variable for each control, and standard event handlers
245 # for standard controls.
246 for widget in topWindow.getElementsByTagName("object"):
247 widgetClass = widget.getAttribute("class")
248 widgetClass = re.sub("^wx", "", widgetClass)
249 widgetName = widget.getAttribute("name")
250 if (widgetName != "" and widgetClass != ""):
251 if widgetClass == "MenuItem":
252 outputList.append(self.templates.CREATE_MENUITEM_VAR % locals())
253 elif widgetClass not in \
254 ['tool', 'unknown', 'notebookpage', 'separator', 'sizeritem']:
255 outputList.append(self.templates.CREATE_WIDGET_VAR % locals())
256 outputList.append('\n\n')
257
258 return "".join(outputList)
259
260 #-------------------------------------------------------------------
261
262 def GenerateInitResourcesEmbedded(self, resourceFilename, resourceDocument):
263 outputList = []
264 files = []
265
266 resourcePath = os.path.split(resourceFilename)[0]
267 memoryPath = self.GetMemoryFilename(os.path.splitext(os.path.split(resourceFilename)[1])[0])
268 resourceFilename = self.GetMemoryFilename(os.path.split(resourceFilename)[1])
269
270 self.ReplaceFilenamesInXRC(resourceDocument.firstChild, files, resourcePath)
271
272 filename = resourceFilename
273 fileData = resourceDocument.toxml() # what about this? encoding=resourceDocument.encoding)
274 outputList.append(self.templates.FILE_AS_STRING % locals())
275
276 for f in files:
277 filename = self.GetMemoryFilename(f)
278 fileData = self.FileToString(os.path.join(resourcePath, f))
279 outputList.append(self.templates.FILE_AS_STRING % locals())
280
281 for f in [resourceFilename] + files:
282 filename = self.GetMemoryFilename(f)
283 outputList.append(self.templates.ADD_FILE_TO_MEMFS % locals())
284
285 outputList.append(self.templates.LOAD_RES_MEMFS % locals())
286
287 return "".join(outputList)
288
289 #-------------------------------------------------------------------
290
291 def GenerateInitResourcesFile(self, resourceFilename, resourceDocument):
292 # take only the filename portion out of resourceFilename
293 resourceFilename = os.path.split(resourceFilename)[1]
294 outputList = []
295 outputList.append(self.templates.LOAD_RES_FILE % locals())
296 return "".join(outputList)
297
298 #-------------------------------------------------------------------
299
300 def GetMemoryFilename(self, filename):
301 # Remove special chars from the filename
302 return re.sub(r"[^A-Za-z0-9_]", "_", filename)
303
304 #-------------------------------------------------------------------
305
306 def FileToString(self, filename):
307 outputList = []
308
309 buffer = open(filename, "rb").read()
310 fileLen = len(buffer)
311
312 linelng = 0
313 for i in xrange(fileLen):
314 s = buffer[i]
315 c = ord(s)
316 if s == '\n':
317 tmp = s
318 linelng = 0
319 elif c < 32 or c > 127 or s == "'":
320 tmp = "\\x%02x" % c
321 elif s == "\\":
322 tmp = "\\\\"
323 else:
324 tmp = s
325
326 if linelng > 70:
327 linelng = 0
328 outputList.append("\\\n")
329
330 outputList.append(tmp)
331 linelng += len(tmp)
332
333 return "".join(outputList)
334
335 #-------------------------------------------------------------------
336
337 def NodeContainsFilename(self, node):
338 """ Does 'node' contain filename information at all? """
339
340 # Any bitmaps:
341 if node.nodeName == "bitmap":
342 return True
343
344 if node.nodeName == "icon":
345 return True
346
347 # URLs in wxHtmlWindow:
348 if node.nodeName == "url":
349 return True
350
351 # wxBitmapButton:
352 parent = node.parentNode
353 if parent.__class__ != minidom.Document and \
354 parent.getAttribute("class") == "wxBitmapButton" and \
355 (node.nodeName == "focus" or node.nodeName == "disabled" or
356 node.nodeName == "selected"):
357 return True
358
359 # wxBitmap or wxIcon toplevel resources:
360 if node.nodeName == "object":
361 klass = node.getAttribute("class")
362 if klass == "wxBitmap" or klass == "wxIcon":
363 return True
364
365 return False
366
367 #-------------------------------------------------------------------
368
369 def ReplaceFilenamesInXRC(self, node, files, resourcePath):
370 """ Finds all files mentioned in resource file, e.g. <bitmap>filename</bitmap>
371 and replaces them with the memory filenames.
372
373 Fills a list of the filenames found."""
374
375 # Is 'node' XML node element?
376 if node is None: return
377 if node.nodeType != minidom.Document.ELEMENT_NODE: return
378
379 containsFilename = self.NodeContainsFilename(node);
380
381 for n in node.childNodes:
382
383 if (containsFilename and
384 (n.nodeType == minidom.Document.TEXT_NODE or
385 n.nodeType == minidom.Document.CDATA_SECTION_NODE)):
386
387 filename = n.nodeValue
388 memoryFilename = self.GetMemoryFilename(filename)
389 n.nodeValue = memoryFilename
390
391 if filename not in files:
392 files.append(filename)
393
394 # Recurse into children
395 if n.nodeType == minidom.Document.ELEMENT_NODE:
396 self.ReplaceFilenamesInXRC(n, files, resourcePath);
397
398 #-------------------------------------------------------------------
399
400 def FindStringsInNode(self, parent):
401 def is_number(st):
402 try:
403 i = int(st)
404 return True
405 except ValueError:
406 return False
407
408 strings = []
409 if parent is None:
410 return strings;
411
412 for child in parent.childNodes:
413 if ((parent.nodeType == parent.ELEMENT_NODE) and
414 # parent is an element, i.e. has subnodes...
415 (child.nodeType == child.TEXT_NODE or
416 child.nodeType == child.CDATA_SECTION_NODE) and
417 # ...it is textnode...
418 (
419 parent.tagName == "label" or
420 (parent.tagName == "value" and
421 not is_number(child.nodeValue)) or
422 parent.tagName == "help" or
423 parent.tagName == "longhelp" or
424 parent.tagName == "tooltip" or
425 parent.tagName == "htmlcode" or
426 parent.tagName == "title" or
427 parent.tagName == "item"
428 )):
429 # ...and known to contain translatable string
430 if (parent.getAttribute("translate") != "0"):
431 strings.append(self.ConvertText(child.nodeValue))
432
433 # subnodes:
434 if child.nodeType == child.ELEMENT_NODE:
435 strings += self.FindStringsInNode(child)
436
437 return strings
438
439 #-------------------------------------------------------------------
440
441 def ConvertText(self, st):
442 st2 = ""
443 dt = list(st)
444
445 skipNext = False
446 for i in range(len(dt)):
447 if skipNext:
448 skipNext = False
449 continue
450
451 if dt[i] == '_':
452 if dt[i+1] == '_':
453 st2 += '_'
454 skipNext = True
455 else:
456 st2 += '&'
457 elif dt[i] == '\n':
458 st2 += '\\n'
459 elif dt[i] == '\t':
460 st2 += '\\t'
461 elif dt[i] == '\r':
462 st2 += '\\r'
463 elif dt[i] == '\\':
464 if dt[i+1] not in ['n', 't', 'r']:
465 st2 += '\\\\'
466 else:
467 st2 += '\\'
468 elif dt[i] == '"':
469 st2 += '\\"'
470 else:
471 st2 += dt[i]
472
473 return st2.encode("UTF-8")
474
475
476 #-------------------------------------------------------------------
477
478 def _OpenOutputFile(self, outputFilename):
479 if outputFilename == "-":
480 outputFile = sys.stdout
481 else:
482 try:
483 outputFile = open(outputFilename, "wt")
484 except IOError:
485 raise IOError("Can't write output to '%s'" % outputFilename)
486 return outputFile
487
488
489
490
491
492 #---------------------------------------------------------------------------
493
494 def main(args):
495 resourceFilename = ""
496 outputFilename = None
497 embedResources = False
498 generateGetText = False
499 generatePython = False
500
501 try:
502 opts, args = getopt.gnu_getopt(args,
503 "hpgeo:",
504 "help python gettext embed output=".split())
505 except getopt.GetoptError, e:
506 print "\nError : %s\n" % str(e)
507 print __doc__
508 sys.exit(1)
509
510 # If there is no input file argument, show help and exit
511 if not args:
512 print __doc__
513 print "No xrc input file was specified."
514 sys.exit(1)
515
516 # Parse options and arguments
517 for opt, val in opts:
518 if opt in ["-h", "--help"]:
519 print __doc__
520 sys.exit(1)
521
522 if opt in ["-p", "--python"]:
523 generatePython = True
524
525 if opt in ["-o", "--output"]:
526 outputFilename = val
527
528 if opt in ["-e", "--embed"]:
529 embedResources = True
530
531 if opt in ["-g", "--gettext"]:
532 generateGetText = True
533
534
535 # check for and expand any wildcards in the list of input files
536 inputFiles = []
537 for arg in args:
538 inputFiles += glob.glob(arg)
539
540
541 comp = XmlResourceCompiler()
542
543 try:
544 if generatePython:
545 if not outputFilename:
546 outputFilename = os.path.splitext(args[0])[0] + "_xrc.py"
547 comp.MakePythonModule(inputFiles, outputFilename,
548 embedResources, generateGetText)
549
550 elif generateGetText:
551 if not outputFilename:
552 outputFilename = '-'
553 comp.MakeGetTextOutput(inputFiles, outputFilename)
554
555 else:
556 print __doc__
557 print "One or both of -p, -g must be specified."
558 sys.exit(1)
559
560
561 except IOError, e:
562 print >>sys.stderr, "%s." % str(e)
563 else:
564 if outputFilename != "-":
565 print >>sys.stderr, "Resources written to %s." % outputFilename
566
567 if __name__ == "__main__":
568 main(sys.argv[1:])
569