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