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