]> git.saurik.com Git - wxWidgets.git/blame - wxPython/wx/tools/pywxrc.py
use wxString in wxFileSystem::FindFileInPath()
[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):
cf952100 59 def PreCreate(self, pre):
73b2a9a7 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()
cf952100 69 self.PreCreate(pre)
73b2a9a7 70 get_resources().LoadOn%(windowClass)s(pre, parent, "%(windowName)s")
73b2a9a7 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)
cffdf649
RD
92
93 # Define variables for the menu items
94"""
95
0b0849b5
RD
96 MENUBAR_CLASS_HEADER = """\
97class 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
cffdf649
RD
105 CREATE_MENUITEM_VAR = """\
106 self.%(widgetName)s = self.FindItemById(xrc.XRCID(\"%(widgetName)s\"))
107"""
108
73b2a9a7 109 INIT_RESOURE_HEADER = """\
73b2a9a7 110# ------------------------ Resource data ----------------------
2e0ae50e 111
73b2a9a7 112def __init_resources():
0fe9c329
RD
113 global __res
114 __res = xrc.EmptyXmlResource()
73b2a9a7 115"""
2e0ae50e 116
73b2a9a7 117 LOAD_RES_FILE = """\
0fe9c329 118 __res.Load('%(resourceFilename)s')"""
2e0ae50e 119
73b2a9a7
RD
120 FILE_AS_STRING = """\
121 %(filename)s = '''\\
122%(fileData)s'''
2e0ae50e 123
73b2a9a7 124"""
2e0ae50e 125
73b2a9a7 126 PREPARE_MEMFS = """\
73b2a9a7
RD
127 wx.FileSystem.AddHandler(wx.MemoryFSHandler())
128"""
2e0ae50e 129
73b2a9a7
RD
130 ADD_FILE_TO_MEMFS = """\
131 wx.MemoryFSHandler.AddFile('XRC/%(memoryPath)s/%(filename)s', %(filename)s)
132"""
2e0ae50e 133
73b2a9a7 134 LOAD_RES_MEMFS = """\
73b2a9a7
RD
135 __res.Load('memory:XRC/%(memoryPath)s/%(resourceFilename)s')
136"""
2e0ae50e 137
0fe9c329 138 GETTEXT_DUMMY_FUNC = """
7e05216c
RD
139# ----------------------- Gettext strings ---------------------
140
141def __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
0fe9c329 150%s
7e05216c
RD
151"""
152
73b2a9a7 153#----------------------------------------------------------------------
2e0ae50e 154
73b2a9a7
RD
155class XmlResourceCompiler:
156
157 templates = PythonTemplates()
2e0ae50e 158
73b2a9a7 159 """This class generates Python code from XML resource files (XRC)."""
2e0ae50e 160
0fe9c329 161 def MakePythonModule(self, inputFiles, outputFilename,
7e05216c 162 embedResources=False, generateGetText=False):
2e0ae50e 163
0fe9c329
RD
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
73b2a9a7 185 print >>outputFile, self.templates.FILE_HEADER
10383fe2
RD
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")
0fe9c329
RD
194
195 print >>outputFile, self.templates.INIT_RESOURE_HEADER
73b2a9a7 196 if embedResources:
0fe9c329 197 print >>outputFile, self.templates.PREPARE_MEMFS
10383fe2
RD
198 resources = u"\n".join(resources)
199 print >>outputFile, resources.encode("UTF-8")
2e0ae50e 200
7e05216c 201 if generateGetText:
10383fe2 202 # These have already been converted to utf-8...
0fe9c329
RD
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)
7e05216c 221
73b2a9a7 222 #-------------------------------------------------------------------
2e0ae50e 223
73b2a9a7
RD
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")
cffdf649 236
0b0849b5
RD
237 if windowClass in ["MenuBar"]:
238 outputList.append(self.templates.MENUBAR_CLASS_HEADER % locals())
239 elif windowClass in ["Menu"]:
cffdf649
RD
240 outputList.append(self.templates.MENU_CLASS_HEADER % locals())
241 else:
242 outputList.append(self.templates.CLASS_HEADER % locals())
2e0ae50e 243
73b2a9a7
RD
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")
cffdf649
RD
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())
73b2a9a7 256 outputList.append('\n\n')
2e0ae50e 257
73b2a9a7 258 return "".join(outputList)
2e0ae50e 259
73b2a9a7 260 #-------------------------------------------------------------------
2e0ae50e 261
73b2a9a7
RD
262 def GenerateInitResourcesEmbedded(self, resourceFilename, resourceDocument):
263 outputList = []
73b2a9a7 264 files = []
2e0ae50e 265
73b2a9a7
RD
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
cffdf649 273 fileData = resourceDocument.toxml() # what about this? encoding=resourceDocument.encoding)
73b2a9a7 274 outputList.append(self.templates.FILE_AS_STRING % locals())
2e0ae50e 275
73b2a9a7
RD
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())
2e0ae50e 280
73b2a9a7
RD
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())
2e0ae50e 286
73b2a9a7
RD
287 return "".join(outputList)
288
289 #-------------------------------------------------------------------
2e0ae50e 290
73b2a9a7 291 def GenerateInitResourcesFile(self, resourceFilename, resourceDocument):
7e05216c
RD
292 # take only the filename portion out of resourceFilename
293 resourceFilename = os.path.split(resourceFilename)[1]
73b2a9a7 294 outputList = []
73b2a9a7
RD
295 outputList.append(self.templates.LOAD_RES_FILE % locals())
296 return "".join(outputList)
2e0ae50e 297
73b2a9a7 298 #-------------------------------------------------------------------
2e0ae50e 299
73b2a9a7
RD
300 def GetMemoryFilename(self, filename):
301 # Remove special chars from the filename
302 return re.sub(r"[^A-Za-z0-9_]", "_", filename)
2e0ae50e 303
73b2a9a7 304 #-------------------------------------------------------------------
2e0ae50e 305
73b2a9a7
RD
306 def FileToString(self, filename):
307 outputList = []
308
2e0ae50e 309 buffer = open(filename, "rb").read()
73b2a9a7 310 fileLen = len(buffer)
2e0ae50e
RD
311
312 linelng = 0
73b2a9a7 313 for i in xrange(fileLen):
2e0ae50e
RD
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
73b2a9a7
RD
328 outputList.append("\\\n")
329
330 outputList.append(tmp)
2e0ae50e 331 linelng += len(tmp)
73b2a9a7
RD
332
333 return "".join(outputList)
334
335 #-------------------------------------------------------------------
2e0ae50e 336
73b2a9a7
RD
337 def NodeContainsFilename(self, node):
338 """ Does 'node' contain filename information at all? """
2e0ae50e 339
73b2a9a7
RD
340 # Any bitmaps:
341 if node.nodeName == "bitmap":
342 return True
2e0ae50e 343
73b2a9a7
RD
344 if node.nodeName == "icon":
345 return True
2e0ae50e 346
73b2a9a7
RD
347 # URLs in wxHtmlWindow:
348 if node.nodeName == "url":
349 return True
2e0ae50e 350
73b2a9a7
RD
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
2e0ae50e 358
73b2a9a7
RD
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
2e0ae50e 364
73b2a9a7 365 return False
2e0ae50e 366
73b2a9a7 367 #-------------------------------------------------------------------
2e0ae50e 368
73b2a9a7
RD
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."""
2e0ae50e 374
73b2a9a7
RD
375 # Is 'node' XML node element?
376 if node is None: return
377 if node.nodeType != minidom.Document.ELEMENT_NODE: return
2e0ae50e 378
73b2a9a7 379 containsFilename = self.NodeContainsFilename(node);
2e0ae50e 380
73b2a9a7 381 for n in node.childNodes:
2e0ae50e 382
73b2a9a7
RD
383 if (containsFilename and
384 (n.nodeType == minidom.Document.TEXT_NODE or
385 n.nodeType == minidom.Document.CDATA_SECTION_NODE)):
2e0ae50e 386
73b2a9a7
RD
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);
2e0ae50e 397
7e05216c
RD
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
10383fe2 473 return st2.encode("UTF-8")
7e05216c
RD
474
475
0fe9c329
RD
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
7e05216c 491
2e0ae50e
RD
492#---------------------------------------------------------------------------
493
73b2a9a7
RD
494def main(args):
495 resourceFilename = ""
0fe9c329 496 outputFilename = None
73b2a9a7 497 embedResources = False
7e05216c 498 generateGetText = False
0fe9c329 499 generatePython = False
73b2a9a7
RD
500
501 try:
0fe9c329
RD
502 opts, args = getopt.gnu_getopt(args,
503 "hpgeo:",
504 "help python gettext embed output=".split())
7e05216c
RD
505 except getopt.GetoptError, e:
506 print "\nError : %s\n" % str(e)
73b2a9a7
RD
507 print __doc__
508 sys.exit(1)
2e0ae50e 509
73b2a9a7 510 # If there is no input file argument, show help and exit
0fe9c329 511 if not args:
73b2a9a7 512 print __doc__
0fe9c329 513 print "No xrc input file was specified."
73b2a9a7
RD
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
0fe9c329
RD
522 if opt in ["-p", "--python"]:
523 generatePython = True
524
73b2a9a7
RD
525 if opt in ["-o", "--output"]:
526 outputFilename = val
527
528 if opt in ["-e", "--embed"]:
529 embedResources = True
530
7e05216c
RD
531 if opt in ["-g", "--gettext"]:
532 generateGetText = True
533
0fe9c329
RD
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
73b2a9a7
RD
540
541 comp = XmlResourceCompiler()
542
543 try:
0fe9c329
RD
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
73b2a9a7
RD
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
2e0ae50e
RD
566
567if __name__ == "__main__":
73b2a9a7 568 main(sys.argv[1:])
2e0ae50e 569