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