]> git.saurik.com Git - wxWidgets.git/blame - wxPython/wx/tools/pywxrc.py
Add support for generating classes for menus
[wxWidgets.git] / wxPython / wx / tools / pywxrc.py
CommitLineData
2e0ae50e
RD
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#
73b2a9a7
RD
10# Massive rework by Eli Golovinsky
11#
2e0ae50e
RD
12# RCS-ID: $Id$
13# Copyright: (c) 2004 by Total Control Software, 2000 Vaclav Slavik
14# Licence: wxWindows license
15#----------------------------------------------------------------------
16
17"""
73b2a9a7 18pywxrc -- Python XML resource compiler
7e05216c 19 (see http://wiki.wxpython.org/index.cgi/pywxrc for more info)
73b2a9a7
RD
20
21Usage: python pywxrc.py -h
0fe9c329 22 python pywxrc.py [-p] [-g] [-e] [-o filename] xrc input files...
73b2a9a7 23
0fe9c329
RD
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
2e0ae50e
RD
29"""
30
73b2a9a7
RD
31import sys, os, getopt, glob, re
32import xml.dom.minidom as minidom
2e0ae50e
RD
33import wx
34import wx.xrc
35
2e0ae50e
RD
36#----------------------------------------------------------------------
37
73b2a9a7
RD
38class PythonTemplates:
39 FILE_HEADER = """\
40# This file was automatically generated by pywxrc, do not edit by hand.
10383fe2 41# -*- coding: UTF-8 -*-
2e0ae50e 42
73b2a9a7
RD
43import wx
44import wx.xrc as xrc
2e0ae50e 45
73b2a9a7 46__res = None
2e0ae50e 47
73b2a9a7
RD
48def 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
2e0ae50e 54
73b2a9a7 55"""
2e0ae50e 56
73b2a9a7 57 CLASS_HEADER = """\
7e05216c 58class xrc%(windowName)s(wx.%(windowClass)s):
73b2a9a7
RD
59 def PreCreate(self):
60 \"\"\" This function is called during the class's initialization.
2e0ae50e 61
73b2a9a7
RD
62 Override it for custom setup before the window is created usually to
63 set additional window styles using SetWindowStyle() and SetExtraStyle().\"\"\"
64 pass
2e0ae50e 65
73b2a9a7
RD
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)
2e0ae50e 72
73b2a9a7
RD
73 # Define variables for the controls
74"""
2e0ae50e 75
73b2a9a7
RD
76 CREATE_WIDGET_VAR = """\
77 self.%(widgetName)s = xrc.XRCCTRL(self, \"%(widgetName)s\")
78"""
2e0ae50e 79
cffdf649
RD
80 MENU_CLASS_HEADER = """\
81class 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
73b2a9a7 102 INIT_RESOURE_HEADER = """\
73b2a9a7 103# ------------------------ Resource data ----------------------
2e0ae50e 104
73b2a9a7 105def __init_resources():
0fe9c329
RD
106 global __res
107 __res = xrc.EmptyXmlResource()
73b2a9a7 108"""
2e0ae50e 109
73b2a9a7 110 LOAD_RES_FILE = """\
0fe9c329 111 __res.Load('%(resourceFilename)s')"""
2e0ae50e 112
73b2a9a7
RD
113 FILE_AS_STRING = """\
114 %(filename)s = '''\\
115%(fileData)s'''
2e0ae50e 116
73b2a9a7 117"""
2e0ae50e 118
73b2a9a7 119 PREPARE_MEMFS = """\
73b2a9a7
RD
120 wx.FileSystem.AddHandler(wx.MemoryFSHandler())
121"""
2e0ae50e 122
73b2a9a7
RD
123 ADD_FILE_TO_MEMFS = """\
124 wx.MemoryFSHandler.AddFile('XRC/%(memoryPath)s/%(filename)s', %(filename)s)
125"""
2e0ae50e 126
73b2a9a7 127 LOAD_RES_MEMFS = """\
73b2a9a7
RD
128 __res.Load('memory:XRC/%(memoryPath)s/%(resourceFilename)s')
129"""
2e0ae50e 130
0fe9c329 131 GETTEXT_DUMMY_FUNC = """
7e05216c
RD
132# ----------------------- Gettext strings ---------------------
133
134def __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
0fe9c329 143%s
7e05216c
RD
144"""
145
73b2a9a7 146#----------------------------------------------------------------------
2e0ae50e 147
73b2a9a7
RD
148class XmlResourceCompiler:
149
150 templates = PythonTemplates()
2e0ae50e 151
73b2a9a7 152 """This class generates Python code from XML resource files (XRC)."""
2e0ae50e 153
0fe9c329 154 def MakePythonModule(self, inputFiles, outputFilename,
7e05216c 155 embedResources=False, generateGetText=False):
2e0ae50e 156
0fe9c329
RD
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
73b2a9a7 178 print >>outputFile, self.templates.FILE_HEADER
10383fe2
RD
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")
0fe9c329
RD
187
188 print >>outputFile, self.templates.INIT_RESOURE_HEADER
73b2a9a7 189 if embedResources:
0fe9c329 190 print >>outputFile, self.templates.PREPARE_MEMFS
10383fe2
RD
191 resources = u"\n".join(resources)
192 print >>outputFile, resources.encode("UTF-8")
2e0ae50e 193
7e05216c 194 if generateGetText:
10383fe2 195 # These have already been converted to utf-8...
0fe9c329
RD
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)
7e05216c 214
73b2a9a7 215 #-------------------------------------------------------------------
2e0ae50e 216
73b2a9a7
RD
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")
cffdf649
RD
229
230 if windowClass == "Menu":
231 outputList.append(self.templates.MENU_CLASS_HEADER % locals())
232 else:
233 outputList.append(self.templates.CLASS_HEADER % locals())
2e0ae50e 234
73b2a9a7
RD
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")
cffdf649
RD
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())
73b2a9a7 247 outputList.append('\n\n')
2e0ae50e 248
73b2a9a7 249 return "".join(outputList)
2e0ae50e 250
73b2a9a7 251 #-------------------------------------------------------------------
2e0ae50e 252
73b2a9a7
RD
253 def GenerateInitResourcesEmbedded(self, resourceFilename, resourceDocument):
254 outputList = []
73b2a9a7 255 files = []
2e0ae50e 256
73b2a9a7
RD
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
cffdf649 264 fileData = resourceDocument.toxml() # what about this? encoding=resourceDocument.encoding)
73b2a9a7 265 outputList.append(self.templates.FILE_AS_STRING % locals())
2e0ae50e 266
73b2a9a7
RD
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())
2e0ae50e 271
73b2a9a7
RD
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())
2e0ae50e 277
73b2a9a7
RD
278 return "".join(outputList)
279
280 #-------------------------------------------------------------------
2e0ae50e 281
73b2a9a7 282 def GenerateInitResourcesFile(self, resourceFilename, resourceDocument):
7e05216c
RD
283 # take only the filename portion out of resourceFilename
284 resourceFilename = os.path.split(resourceFilename)[1]
73b2a9a7 285 outputList = []
73b2a9a7
RD
286 outputList.append(self.templates.LOAD_RES_FILE % locals())
287 return "".join(outputList)
2e0ae50e 288
73b2a9a7 289 #-------------------------------------------------------------------
2e0ae50e 290
73b2a9a7
RD
291 def GetMemoryFilename(self, filename):
292 # Remove special chars from the filename
293 return re.sub(r"[^A-Za-z0-9_]", "_", filename)
2e0ae50e 294
73b2a9a7 295 #-------------------------------------------------------------------
2e0ae50e 296
73b2a9a7
RD
297 def FileToString(self, filename):
298 outputList = []
299
2e0ae50e 300 buffer = open(filename, "rb").read()
73b2a9a7 301 fileLen = len(buffer)
2e0ae50e
RD
302
303 linelng = 0
73b2a9a7 304 for i in xrange(fileLen):
2e0ae50e
RD
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
73b2a9a7
RD
319 outputList.append("\\\n")
320
321 outputList.append(tmp)
2e0ae50e 322 linelng += len(tmp)
73b2a9a7
RD
323
324 return "".join(outputList)
325
326 #-------------------------------------------------------------------
2e0ae50e 327
73b2a9a7
RD
328 def NodeContainsFilename(self, node):
329 """ Does 'node' contain filename information at all? """
2e0ae50e 330
73b2a9a7
RD
331 # Any bitmaps:
332 if node.nodeName == "bitmap":
333 return True
2e0ae50e 334
73b2a9a7
RD
335 if node.nodeName == "icon":
336 return True
2e0ae50e 337
73b2a9a7
RD
338 # URLs in wxHtmlWindow:
339 if node.nodeName == "url":
340 return True
2e0ae50e 341
73b2a9a7
RD
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
2e0ae50e 349
73b2a9a7
RD
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
2e0ae50e 355
73b2a9a7 356 return False
2e0ae50e 357
73b2a9a7 358 #-------------------------------------------------------------------
2e0ae50e 359
73b2a9a7
RD
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."""
2e0ae50e 365
73b2a9a7
RD
366 # Is 'node' XML node element?
367 if node is None: return
368 if node.nodeType != minidom.Document.ELEMENT_NODE: return
2e0ae50e 369
73b2a9a7 370 containsFilename = self.NodeContainsFilename(node);
2e0ae50e 371
73b2a9a7 372 for n in node.childNodes:
2e0ae50e 373
73b2a9a7
RD
374 if (containsFilename and
375 (n.nodeType == minidom.Document.TEXT_NODE or
376 n.nodeType == minidom.Document.CDATA_SECTION_NODE)):
2e0ae50e 377
73b2a9a7
RD
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);
2e0ae50e 388
7e05216c
RD
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
10383fe2 464 return st2.encode("UTF-8")
7e05216c
RD
465
466
0fe9c329
RD
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
7e05216c 482
2e0ae50e
RD
483#---------------------------------------------------------------------------
484
73b2a9a7
RD
485def main(args):
486 resourceFilename = ""
0fe9c329 487 outputFilename = None
73b2a9a7 488 embedResources = False
7e05216c 489 generateGetText = False
0fe9c329 490 generatePython = False
73b2a9a7
RD
491
492 try:
0fe9c329
RD
493 opts, args = getopt.gnu_getopt(args,
494 "hpgeo:",
495 "help python gettext embed output=".split())
7e05216c
RD
496 except getopt.GetoptError, e:
497 print "\nError : %s\n" % str(e)
73b2a9a7
RD
498 print __doc__
499 sys.exit(1)
2e0ae50e 500
73b2a9a7 501 # If there is no input file argument, show help and exit
0fe9c329 502 if not args:
73b2a9a7 503 print __doc__
0fe9c329 504 print "No xrc input file was specified."
73b2a9a7
RD
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
0fe9c329
RD
513 if opt in ["-p", "--python"]:
514 generatePython = True
515
73b2a9a7
RD
516 if opt in ["-o", "--output"]:
517 outputFilename = val
518
519 if opt in ["-e", "--embed"]:
520 embedResources = True
521
7e05216c
RD
522 if opt in ["-g", "--gettext"]:
523 generateGetText = True
524
0fe9c329
RD
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
73b2a9a7
RD
531
532 comp = XmlResourceCompiler()
533
534 try:
0fe9c329
RD
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
73b2a9a7
RD
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
2e0ae50e
RD
557
558if __name__ == "__main__":
73b2a9a7 559 main(sys.argv[1:])
2e0ae50e 560