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