]> git.saurik.com Git - wxWidgets.git/blame - wxPython/wx/tools/pywxrc.py
Allow AutoCompletion for items in collections too
[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
73b2a9a7 80 INIT_RESOURE_HEADER = """\
73b2a9a7 81# ------------------------ Resource data ----------------------
2e0ae50e 82
73b2a9a7 83def __init_resources():
0fe9c329
RD
84 global __res
85 __res = xrc.EmptyXmlResource()
73b2a9a7 86"""
2e0ae50e 87
73b2a9a7 88 LOAD_RES_FILE = """\
0fe9c329 89 __res.Load('%(resourceFilename)s')"""
2e0ae50e 90
73b2a9a7
RD
91 FILE_AS_STRING = """\
92 %(filename)s = '''\\
93%(fileData)s'''
2e0ae50e 94
73b2a9a7 95"""
2e0ae50e 96
73b2a9a7 97 PREPARE_MEMFS = """\
73b2a9a7
RD
98 wx.FileSystem.AddHandler(wx.MemoryFSHandler())
99"""
2e0ae50e 100
73b2a9a7
RD
101 ADD_FILE_TO_MEMFS = """\
102 wx.MemoryFSHandler.AddFile('XRC/%(memoryPath)s/%(filename)s', %(filename)s)
103"""
2e0ae50e 104
73b2a9a7 105 LOAD_RES_MEMFS = """\
73b2a9a7
RD
106 __res.Load('memory:XRC/%(memoryPath)s/%(resourceFilename)s')
107"""
2e0ae50e 108
0fe9c329 109 GETTEXT_DUMMY_FUNC = """
7e05216c
RD
110# ----------------------- Gettext strings ---------------------
111
112def __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
0fe9c329 121%s
7e05216c
RD
122"""
123
73b2a9a7 124#----------------------------------------------------------------------
2e0ae50e 125
73b2a9a7
RD
126class XmlResourceCompiler:
127
128 templates = PythonTemplates()
2e0ae50e 129
73b2a9a7 130 """This class generates Python code from XML resource files (XRC)."""
2e0ae50e 131
0fe9c329 132 def MakePythonModule(self, inputFiles, outputFilename,
7e05216c 133 embedResources=False, generateGetText=False):
2e0ae50e 134
0fe9c329
RD
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
73b2a9a7 156 print >>outputFile, self.templates.FILE_HEADER
10383fe2
RD
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")
0fe9c329
RD
165
166 print >>outputFile, self.templates.INIT_RESOURE_HEADER
73b2a9a7 167 if embedResources:
0fe9c329 168 print >>outputFile, self.templates.PREPARE_MEMFS
10383fe2
RD
169 resources = u"\n".join(resources)
170 print >>outputFile, resources.encode("UTF-8")
2e0ae50e 171
7e05216c 172 if generateGetText:
10383fe2 173 # These have already been converted to utf-8...
0fe9c329
RD
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)
7e05216c 192
73b2a9a7 193 #-------------------------------------------------------------------
2e0ae50e 194
73b2a9a7
RD
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())
2e0ae50e 208
73b2a9a7
RD
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')
2e0ae50e 221
73b2a9a7 222 return "".join(outputList)
2e0ae50e 223
73b2a9a7 224 #-------------------------------------------------------------------
2e0ae50e 225
73b2a9a7
RD
226 def GenerateInitResourcesEmbedded(self, resourceFilename, resourceDocument):
227 outputList = []
73b2a9a7 228 files = []
2e0ae50e 229
73b2a9a7
RD
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())
2e0ae50e 239
73b2a9a7
RD
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())
2e0ae50e 244
73b2a9a7
RD
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())
2e0ae50e 250
73b2a9a7
RD
251 return "".join(outputList)
252
253 #-------------------------------------------------------------------
2e0ae50e 254
73b2a9a7 255 def GenerateInitResourcesFile(self, resourceFilename, resourceDocument):
7e05216c
RD
256 # take only the filename portion out of resourceFilename
257 resourceFilename = os.path.split(resourceFilename)[1]
73b2a9a7 258 outputList = []
73b2a9a7
RD
259 outputList.append(self.templates.LOAD_RES_FILE % locals())
260 return "".join(outputList)
2e0ae50e 261
73b2a9a7 262 #-------------------------------------------------------------------
2e0ae50e 263
73b2a9a7
RD
264 def GetMemoryFilename(self, filename):
265 # Remove special chars from the filename
266 return re.sub(r"[^A-Za-z0-9_]", "_", filename)
2e0ae50e 267
73b2a9a7 268 #-------------------------------------------------------------------
2e0ae50e 269
73b2a9a7
RD
270 def FileToString(self, filename):
271 outputList = []
272
2e0ae50e 273 buffer = open(filename, "rb").read()
73b2a9a7 274 fileLen = len(buffer)
2e0ae50e
RD
275
276 linelng = 0
73b2a9a7 277 for i in xrange(fileLen):
2e0ae50e
RD
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
73b2a9a7
RD
292 outputList.append("\\\n")
293
294 outputList.append(tmp)
2e0ae50e 295 linelng += len(tmp)
73b2a9a7
RD
296
297 return "".join(outputList)
298
299 #-------------------------------------------------------------------
2e0ae50e 300
73b2a9a7
RD
301 def NodeContainsFilename(self, node):
302 """ Does 'node' contain filename information at all? """
2e0ae50e 303
73b2a9a7
RD
304 # Any bitmaps:
305 if node.nodeName == "bitmap":
306 return True
2e0ae50e 307
73b2a9a7
RD
308 if node.nodeName == "icon":
309 return True
2e0ae50e 310
73b2a9a7
RD
311 # URLs in wxHtmlWindow:
312 if node.nodeName == "url":
313 return True
2e0ae50e 314
73b2a9a7
RD
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
2e0ae50e 322
73b2a9a7
RD
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
2e0ae50e 328
73b2a9a7 329 return False
2e0ae50e 330
73b2a9a7 331 #-------------------------------------------------------------------
2e0ae50e 332
73b2a9a7
RD
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."""
2e0ae50e 338
73b2a9a7
RD
339 # Is 'node' XML node element?
340 if node is None: return
341 if node.nodeType != minidom.Document.ELEMENT_NODE: return
2e0ae50e 342
73b2a9a7 343 containsFilename = self.NodeContainsFilename(node);
2e0ae50e 344
73b2a9a7 345 for n in node.childNodes:
2e0ae50e 346
73b2a9a7
RD
347 if (containsFilename and
348 (n.nodeType == minidom.Document.TEXT_NODE or
349 n.nodeType == minidom.Document.CDATA_SECTION_NODE)):
2e0ae50e 350
73b2a9a7
RD
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);
2e0ae50e 361
7e05216c
RD
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
10383fe2 437 return st2.encode("UTF-8")
7e05216c
RD
438
439
0fe9c329
RD
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
7e05216c 455
2e0ae50e
RD
456#---------------------------------------------------------------------------
457
73b2a9a7
RD
458def main(args):
459 resourceFilename = ""
0fe9c329 460 outputFilename = None
73b2a9a7 461 embedResources = False
7e05216c 462 generateGetText = False
0fe9c329 463 generatePython = False
73b2a9a7
RD
464
465 try:
0fe9c329
RD
466 opts, args = getopt.gnu_getopt(args,
467 "hpgeo:",
468 "help python gettext embed output=".split())
7e05216c
RD
469 except getopt.GetoptError, e:
470 print "\nError : %s\n" % str(e)
73b2a9a7
RD
471 print __doc__
472 sys.exit(1)
2e0ae50e 473
73b2a9a7 474 # If there is no input file argument, show help and exit
0fe9c329 475 if not args:
73b2a9a7 476 print __doc__
0fe9c329 477 print "No xrc input file was specified."
73b2a9a7
RD
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
0fe9c329
RD
486 if opt in ["-p", "--python"]:
487 generatePython = True
488
73b2a9a7
RD
489 if opt in ["-o", "--output"]:
490 outputFilename = val
491
492 if opt in ["-e", "--embed"]:
493 embedResources = True
494
7e05216c
RD
495 if opt in ["-g", "--gettext"]:
496 generateGetText = True
497
0fe9c329
RD
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
73b2a9a7
RD
504
505 comp = XmlResourceCompiler()
506
507 try:
0fe9c329
RD
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
73b2a9a7
RD
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
2e0ae50e
RD
530
531if __name__ == "__main__":
73b2a9a7 532 main(sys.argv[1:])
2e0ae50e 533