mirror of
https://github.com/wxWidgets/Phoenix.git
synced 2026-01-05 03:20:08 +01:00
It seems that Python 3.12 has started emitting additional SyntaxWarnings in cases where there are backslashes used in strings, so change these strings to raw strings to avoid the warnings.
950 lines
33 KiB
Python
950 lines
33 KiB
Python
#----------------------------------------------------------------------
|
|
# Name: wx.tools.pywxrc
|
|
# Purpose: XML resource compiler
|
|
#
|
|
# Author: Robin Dunn
|
|
# Based on wxrc.cpp by Vaclav Slavik, Eduardo Marques
|
|
# Ported to Python in order to not require yet another
|
|
# binary in wxPython distributions
|
|
#
|
|
# Massive rework by Eli Golovinsky
|
|
#
|
|
# Editable blocks by Roman Rolinsky
|
|
#
|
|
# Copyright: (c) 2004-2020 by Total Control Software, 2000 Vaclav Slavik
|
|
# Licence: wxWindows license
|
|
# Tags: phoenix-port
|
|
#----------------------------------------------------------------------
|
|
|
|
"""
|
|
pywxrc -- Python XML resource compiler
|
|
(see http://wiki.wxpython.org/index.cgi/pywxrc for more info)
|
|
|
|
Usage: python pywxrc.py -h
|
|
python pywxrc.py [-p] [-g] [-e] [-v] [-o filename] xrc input files...
|
|
|
|
-h, --help show help message
|
|
-p, --python generate python module
|
|
-g, --gettext output list of translatable strings (may be combined with -p)
|
|
-e, --embed embed XRC resources in the output file
|
|
-v, --novar suppress default assignment of variables
|
|
-o, --output output filename, or - for stdout
|
|
"""
|
|
|
|
import sys, os, getopt, glob, re
|
|
import xml.dom.minidom as minidom
|
|
from six import print_, byte2int
|
|
|
|
#----------------------------------------------------------------------
|
|
|
|
reBeginBlock = re.compile(r'^#!XRCED:begin-block:(\S+)')
|
|
reEndBlock = re.compile(r'^#!XRCED:end-block:(\S+)')
|
|
|
|
class PythonTemplates:
|
|
FILE_HEADER = """\
|
|
# This file was automatically generated by pywxrc.
|
|
# -*- coding: UTF-8 -*-
|
|
|
|
import wx
|
|
import wx.xrc as xrc
|
|
|
|
__res = None
|
|
|
|
def get_resources():
|
|
\"\"\" This function provides access to the XML resources in this module.\"\"\"
|
|
global __res
|
|
if __res is None:
|
|
__init_resources()
|
|
return __res
|
|
|
|
"""
|
|
|
|
CLASS_HEADER = """\
|
|
class xrc%(windowName)s(wx.%(windowClass)s):
|
|
#!XRCED:begin-block:xrc%(windowName)s.PreCreate
|
|
def PreCreate(self):
|
|
\"\"\" This function is called during the class's initialization.
|
|
|
|
Override it for custom setup before the window is created usually to
|
|
set additional window styles using SetWindowStyle() and SetExtraStyle().
|
|
\"\"\"
|
|
pass
|
|
|
|
#!XRCED:end-block:xrc%(windowName)s.PreCreate
|
|
|
|
def __init__(self, parent):
|
|
wx.%(windowClass)s.__init__(self)
|
|
self.PreCreate()
|
|
get_resources().Load%(windowClass)s(self, parent, "%(windowName)s")
|
|
|
|
# Define variables for the controls, bind event handlers
|
|
"""
|
|
|
|
SUBCLASS_HEADER = """\
|
|
class %(subclass)s(wx.%(windowClass)s):
|
|
def __init__(self):
|
|
wx.%(windowClass)s.__init__(self)
|
|
self.Bind(wx.EVT_WINDOW_CREATE, self.OnCreate)
|
|
|
|
#!XRCED:begin-block:%(subclass)s._PostInit
|
|
def _PostInit(self):
|
|
\"\"\" This function is called after the subclassed object is created.
|
|
|
|
Override it for custom setup before the window is created usually to
|
|
set additional window styles using SetWindowStyle() and SetExtraStyle().
|
|
\"\"\"
|
|
pass
|
|
#!XRCED:end-block:%(subclass)s._PostInit
|
|
|
|
def OnCreate(self, evt):
|
|
self.Unbind(wx.EVT_WINDOW_CREATE)
|
|
self._PostInit()
|
|
"""
|
|
|
|
CREATE_WIDGET_VAR = """\
|
|
self.%(widgetName)s = xrc.XRCCTRL(self, \"%(widgetName)s\")
|
|
"""
|
|
|
|
FRAME_MENUBAR_VAR = """\
|
|
self.%(widgetName)s = self.GetMenuBar()
|
|
"""
|
|
|
|
FRAME_MENUBAR_MENUITEM_VAR = """\
|
|
self.%(widgetName)s = self.GetMenuBar().FindItemById(xrc.XRCID(\"%(widgetName)s\"))
|
|
"""
|
|
|
|
FRAME_MENUBAR_MENU_VAR = """\
|
|
idx = self.GetMenuBar().FindMenu(\"%(label)s\")
|
|
if idx != wx.NOT_FOUND:
|
|
self.%(widgetName)s = self.GetMenuBar().GetMenu(idx)
|
|
else:
|
|
self.%(widgetName)s = self.GetMenuBar().FindItemById(xrc.XRCID(\"%(widgetName)s\")).GetSubMenu()
|
|
"""
|
|
|
|
|
|
MENUBAR_MENUITEM_VAR = """\
|
|
self.%(widgetName)s = self.FindItemById(xrc.XRCID(\"%(widgetName)s\"))
|
|
"""
|
|
|
|
MENUBAR_MENU_VAR = """\
|
|
idx = self.FindMenu(\"%(label)s\")
|
|
if idx != wx.NOT_FOUND:
|
|
self.%(widgetName)s = self.GetMenu(idx)
|
|
else:
|
|
self.%(widgetName)s = self.FindItemById(xrc.XRCID(\"%(widgetName)s\")).GetSubMenu()
|
|
"""
|
|
|
|
MENU_MENUITEM_VAR = """\
|
|
self.%(widgetName)s = self.FindItemById(xrc.XRCID(\"%(widgetName)s\"))
|
|
"""
|
|
|
|
MENU_MENU_VAR = """\
|
|
self.%(widgetName)s = self.FindItemById(xrc.XRCID(\"%(widgetName)s\")).GetSubMenu()
|
|
"""
|
|
|
|
FRAME_TOOLBAR_VAR = """\
|
|
self.%(widgetName)s = self.GetToolBar()
|
|
"""
|
|
|
|
FRAME_TOOLBAR_TOOL_VAR = """\
|
|
self.%(widgetName)s = self.GetToolBar().FindById(xrc.XRCID(\"%(widgetName)s\"))
|
|
"""
|
|
|
|
TOOLBAR_TOOL_VAR = """\
|
|
self.%(widgetName)s = self.FindById(xrc.XRCID(\"%(widgetName)s\"))
|
|
"""
|
|
|
|
BIND_WIDGET_EVENT = """\
|
|
self.Bind(wx.%(event)s, self.%(eventHandler)s, %(eventObject)s)
|
|
"""
|
|
|
|
BIND_EVENT = """\
|
|
self.Bind(wx.%(event)s, self.%(eventHandler)s)
|
|
"""
|
|
|
|
CREATE_EVENT_HANDLER = """\
|
|
#!XRCED:begin-block:xrc%(windowName)s.%(eventHandler)s
|
|
def %(eventHandler)s(self, evt):
|
|
# Replace with event handler code
|
|
print(\"%(eventHandler)s()\")
|
|
#!XRCED:end-block:xrc%(windowName)s.%(eventHandler)s
|
|
"""
|
|
|
|
MENU_CLASS_HEADER = """\
|
|
class xrc%(windowName)s(wx.%(windowClass)s):
|
|
def __init__(self):
|
|
pre = get_resources().LoadMenu("%(windowName)s")
|
|
|
|
# This is a copy of Robin's PostCreate voodoo magic in wx.Window that
|
|
# relinks the self object with the menu object.
|
|
self.this = pre.this
|
|
self.thisown = pre.thisown
|
|
pre.thisown = 0
|
|
if hasattr(self, '_setOORInfo'):
|
|
self._setOORInfo(self)
|
|
|
|
# Define variables for the menu items
|
|
"""
|
|
|
|
MENUBAR_CLASS_HEADER = """\
|
|
class xrc%(windowName)s(wx.%(windowClass)s):
|
|
def __init__(self):
|
|
pre = get_resources().LoadMenuBar("%(windowName)s")
|
|
self.PostCreate(pre)
|
|
|
|
# Define variables for the menu items
|
|
"""
|
|
|
|
TOOLBAR_CLASS_HEADER = """\
|
|
class xrc%(windowName)s(wx.%(windowClass)s):
|
|
def __init__(self, parent):
|
|
pre = get_resources().LoadToolBar(parent, "%(windowName)s")
|
|
self.PostCreate(pre)
|
|
|
|
# Define variables for the toolbar items
|
|
"""
|
|
|
|
INIT_RESOURE_HEADER = """\
|
|
# ------------------------ Resource data ----------------------
|
|
|
|
def __init_resources():
|
|
global __res
|
|
__res = xrc.XmlResource()
|
|
"""
|
|
|
|
LOAD_RES_FILE = """\
|
|
__res.Load('%(resourceFilename)s')"""
|
|
|
|
FILE_AS_STRING = """\
|
|
%(filename)s = b'''\\
|
|
%(fileData)s'''
|
|
|
|
"""
|
|
|
|
PREPARE_MEMFS = """\
|
|
wx.FileSystem.AddHandler(wx.MemoryFSHandler())
|
|
"""
|
|
|
|
ADD_FILE_TO_MEMFS = """\
|
|
wx.MemoryFSHandler.AddFile('XRC/%(memoryPath)s/%(filename)s', memoryview(%(filename)s))
|
|
"""
|
|
|
|
LOAD_RES_MEMFS = """\
|
|
__res.Load('memory:XRC/%(memoryPath)s/%(resourceFilename)s')
|
|
"""
|
|
|
|
GETTEXT_DUMMY_FUNC = """
|
|
# ----------------------- Gettext strings ---------------------
|
|
|
|
def __gettext_strings():
|
|
# This is a dummy function that lists all the strings that are used in
|
|
# the XRC file in the _("a string") format to be recognized by GNU
|
|
# gettext utilities (specifically the xgettext utility) and the
|
|
# mki18n.py script. For more information see:
|
|
# http://wiki.wxpython.org/index.cgi/Internationalization
|
|
|
|
def _(str): pass
|
|
|
|
%s
|
|
"""
|
|
|
|
#----------------------------------------------------------------------
|
|
|
|
class XmlResourceCompiler:
|
|
|
|
templates = PythonTemplates()
|
|
|
|
"""This class generates Python code from XML resource files (XRC)."""
|
|
|
|
def MakePythonModule(self, inputFiles, outputFilename,
|
|
embedResources=False, generateGetText=False,
|
|
assignVariables=True):
|
|
|
|
self.blocks = {}
|
|
self.outputFilename = outputFilename
|
|
outputFile = self._OpenOutputFile(outputFilename)
|
|
self.assignVariables = assignVariables
|
|
|
|
classes = []
|
|
subclasses = []
|
|
resources = []
|
|
gettextStrings = []
|
|
|
|
# process all the inputFiles, collecting the output data
|
|
for inFile in inputFiles:
|
|
resourceDocument = minidom.parse(inFile)
|
|
subclasses.append(self.GenerateSubclasses(resourceDocument))
|
|
classes.append(self.GenerateClasses(resourceDocument))
|
|
|
|
if embedResources:
|
|
res = self.GenerateInitResourcesEmbedded(inFile, resourceDocument)
|
|
else:
|
|
res = self.GenerateInitResourcesFile(inFile, resourceDocument)
|
|
resources.append(res)
|
|
|
|
if generateGetText:
|
|
gettextStrings += self.FindStringsInNode(resourceDocument.firstChild)
|
|
|
|
# now write it all out
|
|
print_(self.templates.FILE_HEADER, file=outputFile)
|
|
|
|
# Note: Technically it is not legal to have anything other
|
|
# than ascii for class and variable names, but since the user
|
|
# can create the XML with non-ascii names we'll go ahead and
|
|
# allow for it here, and then let Python complain about it
|
|
# later when they try to run the program.
|
|
if subclasses:
|
|
subclasses = self.ReplaceBlocks(u"\n".join(subclasses))
|
|
print_(subclasses, file=outputFile)
|
|
if classes:
|
|
classes = self.ReplaceBlocks(u"\n".join(classes))
|
|
print_(classes, file=outputFile)
|
|
|
|
print_(self.templates.INIT_RESOURE_HEADER, file=outputFile)
|
|
if embedResources:
|
|
print_(self.templates.PREPARE_MEMFS, file=outputFile)
|
|
resources = u"\n".join(resources)
|
|
print_(resources, file=outputFile)
|
|
|
|
if generateGetText:
|
|
# gettextStrings is a list of unicode strings as returned by ConvertText
|
|
conversions = [u' _("%s")' % s for s in gettextStrings]
|
|
conversion_block = u"\n".join(conversions)
|
|
conversion_func = self.templates.GETTEXT_DUMMY_FUNC % conversion_block
|
|
print_(conversion_func, file=outputFile)
|
|
|
|
#-------------------------------------------------------------------
|
|
|
|
def MakeGetTextOutput(self, inputFiles, outputFilename):
|
|
"""
|
|
Just output the gettext strings by themselves, with no other
|
|
code generation.
|
|
"""
|
|
outputFile = self._OpenOutputFile(outputFilename)
|
|
for inFile in inputFiles:
|
|
resourceDocument = minidom.parse(inFile)
|
|
resource = resourceDocument.firstChild
|
|
strings = self.FindStringsInNode(resource)
|
|
# strings is a list of unicode strings as returned by ConvertText
|
|
strings = ['_("%s");' % s for s in strings]
|
|
print_("\n".join(strings), file=outputFile)
|
|
|
|
#-------------------------------------------------------------------
|
|
|
|
def GenerateClasses(self, resourceDocument):
|
|
outputList = []
|
|
|
|
resource = resourceDocument.firstChild
|
|
topWindows = [e for e in resource.childNodes
|
|
if e.nodeType == e.ELEMENT_NODE and e.tagName == "object" \
|
|
and not e.getAttribute('subclass')]
|
|
|
|
# Generate a class for each top-window object (Frame, Panel, Dialog, etc.)
|
|
for topWindow in topWindows:
|
|
windowClass = topWindow.getAttribute("class")
|
|
windowClass = re.sub("^wx", "", windowClass)
|
|
windowName = topWindow.getAttribute("name")
|
|
if not windowName: continue
|
|
|
|
if windowClass in ["MenuBar"]:
|
|
genfunc = self.GenerateMenuBarClass
|
|
elif windowClass in ["Menu"]:
|
|
genfunc = self.GenerateMenuClass
|
|
elif windowClass in ["ToolBar"]:
|
|
genfunc = self.GenerateToolBarClass
|
|
else:
|
|
genfunc = self.GenerateWidgetClass
|
|
|
|
vars = []
|
|
outputList += genfunc(windowClass, windowName, topWindow, vars)
|
|
outputList.append('\n')
|
|
|
|
outputList += self.GenerateEventHandlers(windowClass, windowName, topWindow, vars)
|
|
|
|
return "".join(outputList)
|
|
|
|
#-------------------------------------------------------------------
|
|
|
|
def CheckAssignVar(self, widget):
|
|
if self.assignVariables: return True # assign_var override mode
|
|
assign_var = False
|
|
for node in widget.getElementsByTagName("XRCED"):
|
|
if node.parentNode is widget:
|
|
try:
|
|
elem = node.getElementsByTagName("assign_var")[0]
|
|
except IndexError:
|
|
continue
|
|
if elem.childNodes:
|
|
ch = elem.childNodes[0]
|
|
if ch.nodeType == ch.TEXT_NODE and bool(ch.nodeValue):
|
|
assign_var = True
|
|
return assign_var
|
|
|
|
|
|
def GenerateMenuBarClass(self, windowClass, windowName, topWindow, vars):
|
|
outputList = []
|
|
|
|
# output the header
|
|
outputList.append(self.templates.MENUBAR_CLASS_HEADER % locals())
|
|
|
|
# create an attribute for menus and menu items that have names
|
|
for widget in topWindow.getElementsByTagName("object"):
|
|
if not self.CheckAssignVar(widget): continue
|
|
widgetClass = widget.getAttribute("class")
|
|
widgetClass = re.sub("^wx", "", widgetClass)
|
|
widgetName = widget.getAttribute("name")
|
|
if widgetName != "" and widgetClass != "":
|
|
vars.append(widgetName)
|
|
if widgetClass == "MenuItem":
|
|
outputList.append(self.templates.MENUBAR_MENUITEM_VAR % locals())
|
|
elif widgetClass == "Menu":
|
|
for e in widget.childNodes:
|
|
if e.nodeType == e.ELEMENT_NODE and e.tagName == "label":
|
|
label = e
|
|
break
|
|
label = label.childNodes[0].data
|
|
outputList.append(self.templates.MENUBAR_MENU_VAR % locals())
|
|
else:
|
|
raise RuntimeError("Unexpected widgetClass for MenuBar: %s" % widgetClass)
|
|
|
|
return outputList
|
|
|
|
|
|
def GenerateMenuClass(self, windowClass, windowName, topWindow, vars):
|
|
outputList = []
|
|
|
|
# output the header
|
|
outputList.append(self.templates.MENU_CLASS_HEADER % locals())
|
|
for widget in topWindow.getElementsByTagName("object"):
|
|
if not self.CheckAssignVar(widget): continue
|
|
widgetClass = widget.getAttribute("class")
|
|
widgetClass = re.sub("^wx", "", widgetClass)
|
|
widgetName = widget.getAttribute("name")
|
|
if widgetName != "" and widgetClass != "":
|
|
vars.append(widgetName)
|
|
if widgetClass == "MenuItem":
|
|
outputList.append(self.templates.MENU_MENUITEM_VAR % locals())
|
|
elif widgetClass == "Menu":
|
|
for e in widget.childNodes:
|
|
if e.nodeType == e.ELEMENT_NODE and e.tagName == "label":
|
|
label = e
|
|
break
|
|
label = label.childNodes[0].data
|
|
outputList.append(self.templates.MENU_MENU_VAR % locals())
|
|
else:
|
|
raise RuntimeError("Unexpected widgetClass for Menu: %s" % widgetClass)
|
|
|
|
return outputList
|
|
|
|
|
|
def GenerateToolBarClass(self, windowClass, windowName, topWindow, vars):
|
|
outputList = []
|
|
|
|
# output the header
|
|
outputList.append(self.templates.TOOLBAR_CLASS_HEADER % locals())
|
|
|
|
# create an attribute for menus and menu items that have names
|
|
for widget in topWindow.getElementsByTagName("object"):
|
|
if not self.CheckAssignVar(widget): continue
|
|
widgetClass = widget.getAttribute("class")
|
|
widgetClass = re.sub("^wx", "", widgetClass)
|
|
widgetName = widget.getAttribute("name")
|
|
if widgetName != "" and widgetClass != "":
|
|
vars.append(widgetName)
|
|
if widgetClass == "tool":
|
|
outputList.append(self.templates.TOOLBAR_TOOL_VAR % locals())
|
|
else:
|
|
raise RuntimeError("Unexpected widgetClass for ToolBar: %s" % widgetClass)
|
|
|
|
return outputList
|
|
|
|
|
|
def GenerateWidgetClass(self, windowClass, windowName, topWindow, vars):
|
|
outputList = []
|
|
|
|
# output the header
|
|
outputList.append(self.templates.CLASS_HEADER % locals())
|
|
|
|
# Generate an attribute for each named item in the container
|
|
for widget in topWindow.getElementsByTagName("object"):
|
|
if not self.CheckAssignVar(widget): continue
|
|
widgetClass = widget.getAttribute("class")
|
|
widgetClass = re.sub("^wx", "", widgetClass)
|
|
widgetName = widget.getAttribute("name")
|
|
if widgetName != "" and widgetClass != "":
|
|
vars.append(widgetName)
|
|
if widgetClass == "MenuBar":
|
|
outputList.append(self.templates.FRAME_MENUBAR_VAR % locals())
|
|
elif widgetClass == "MenuItem":
|
|
outputList.append(self.templates.FRAME_MENUBAR_MENUITEM_VAR % locals())
|
|
elif widgetClass == "Menu":
|
|
# Only look directly under for the "label"
|
|
for e in widget.childNodes:
|
|
if e.nodeType == e.ELEMENT_NODE and e.tagName == "label":
|
|
label = e
|
|
break
|
|
label = label.childNodes[0].data
|
|
outputList.append(self.templates.FRAME_MENUBAR_MENU_VAR % locals())
|
|
# elif widgetClass == "ToolBar":
|
|
# outputList.append(self.templates.FRAME_TOOLBAR_VAR % locals())
|
|
elif widgetClass == "tool":
|
|
outputList.append(self.templates.FRAME_TOOLBAR_TOOL_VAR % locals())
|
|
elif widgetClass in ('unknown', 'notebookpage', 'separator', 'sizeritem'):
|
|
pass
|
|
else:
|
|
outputList.append(self.templates.CREATE_WIDGET_VAR % locals())
|
|
|
|
return outputList
|
|
|
|
#-------------------------------------------------------------------
|
|
|
|
def GenerateSubclasses(self, resourceDocument):
|
|
outputList = []
|
|
|
|
objectNodes = resourceDocument.getElementsByTagName("object")
|
|
subclasses = set()
|
|
bases = {}
|
|
baseName = os.path.splitext(self.outputFilename)[0]
|
|
for node in objectNodes:
|
|
subclass = node.getAttribute('subclass')
|
|
if subclass:
|
|
module = subclass[:subclass.find('.')]
|
|
if module != baseName: continue
|
|
klass = node.getAttribute("class")
|
|
if subclass not in subclasses:
|
|
subclasses.add(subclass)
|
|
bases[subclass] = klass
|
|
else:
|
|
if klass != bases[subclass]:
|
|
print('pywxrc: error: conflicting base classes for subclass %(subclass)s' % subclass)
|
|
|
|
# Generate subclasses
|
|
for subclass in subclasses:
|
|
windowClass = bases[subclass]
|
|
subclass = re.sub(r"^\S+\.", "", subclass)
|
|
windowClass = re.sub("^wx", "", windowClass)
|
|
outputList.append(self.templates.SUBCLASS_HEADER % locals())
|
|
outputList.append('\n')
|
|
|
|
return "".join(outputList)
|
|
|
|
#-------------------------------------------------------------------
|
|
|
|
def GenerateEventHandlers(self, windowClass, windowName, topWindow, vars):
|
|
outputList = []
|
|
|
|
# Generate child event handlers
|
|
eventHandlers = []
|
|
for elem in topWindow.getElementsByTagName("XRCED"):
|
|
try:
|
|
eventNode = elem.getElementsByTagName("events")[0]
|
|
except IndexError:
|
|
continue
|
|
events = eventNode.childNodes[0].data.split('|')
|
|
for event in events:
|
|
if elem.parentNode is topWindow:
|
|
eventHandler = "On%s" % event[4:].capitalize()
|
|
outputList.append(self.templates.BIND_EVENT % locals())
|
|
eventHandlers.append(eventHandler)
|
|
else:
|
|
widget = elem.parentNode
|
|
widgetClass = widget.getAttribute("class")
|
|
widgetClass = re.sub("^wx", "", widgetClass)
|
|
widgetName = widget.getAttribute("name")
|
|
if not widgetName or not widgetClass: continue
|
|
eventObject = None
|
|
if widgetClass == "MenuItem" and windowClass != "MenuBar":
|
|
if widgetName[:2] == "wx":
|
|
eventObject = 'id=wx.%s' % re.sub("^wx", "", widgetName)
|
|
eventHandler = "On%s_%s" % (event[4:].capitalize(), widgetName)
|
|
if widgetName in vars: eventObject = "self.%s" % widgetName
|
|
else:
|
|
eventHandler = "On%s_%s" % (event[4:].capitalize(), widgetName)
|
|
if widgetName in vars: eventObject = "self.%s" % widgetName
|
|
if not eventObject:
|
|
eventObject = "id=xrc.XRCID('%s')" % widgetName
|
|
outputList.append(self.templates.BIND_WIDGET_EVENT % locals())
|
|
eventHandlers.append(eventHandler)
|
|
outputList.append("\n")
|
|
|
|
for eventHandler in eventHandlers:
|
|
block = "xrc%(windowName)s.%(eventHandler)s" % locals()
|
|
try:
|
|
outputList.append(self.blocks[block])
|
|
except KeyError:
|
|
outputList.append(self.templates.CREATE_EVENT_HANDLER % locals())
|
|
outputList.append("\n")
|
|
|
|
outputList.append("\n")
|
|
return "".join(outputList)
|
|
|
|
#-------------------------------------------------------------------
|
|
|
|
def GenerateInitResourcesEmbedded(self, resourceFilename, resourceDocument):
|
|
outputList = []
|
|
files = []
|
|
|
|
resourcePath = os.path.split(resourceFilename)[0]
|
|
memoryPath = self.GetMemoryFilename(os.path.splitext(os.path.split(resourceFilename)[1])[0])
|
|
resourceFilename = self.GetMemoryFilename(os.path.split(resourceFilename)[1])
|
|
|
|
self.ReplaceFilenamesInXRC(resourceDocument.firstChild, files, resourcePath)
|
|
|
|
filename = resourceFilename
|
|
fileData = resourceDocument.toxml() # what about this? encoding=resourceDocument.encoding)
|
|
outputList.append(self.templates.FILE_AS_STRING % locals())
|
|
|
|
for f in files:
|
|
filename = self.GetMemoryFilename(f)
|
|
fileData = self.FileToString(os.path.join(resourcePath, f))
|
|
outputList.append(self.templates.FILE_AS_STRING % locals())
|
|
|
|
for f in [resourceFilename] + files:
|
|
filename = self.GetMemoryFilename(f)
|
|
outputList.append(self.templates.ADD_FILE_TO_MEMFS % locals())
|
|
|
|
outputList.append(self.templates.LOAD_RES_MEMFS % locals())
|
|
|
|
return "".join(outputList)
|
|
|
|
#-------------------------------------------------------------------
|
|
|
|
def GenerateInitResourcesFile(self, resourceFilename, resourceDocument):
|
|
# take only the filename portion out of resourceFilename
|
|
resourceFilename = os.path.split(resourceFilename)[1]
|
|
outputList = []
|
|
outputList.append(self.templates.LOAD_RES_FILE % locals())
|
|
return "".join(outputList)
|
|
|
|
#-------------------------------------------------------------------
|
|
|
|
def GetMemoryFilename(self, filename):
|
|
# Remove special chars from the filename
|
|
return re.sub(r"[^A-Za-z0-9_]", "_", filename)
|
|
|
|
#-------------------------------------------------------------------
|
|
|
|
def FileToString(self, filename):
|
|
outputList = []
|
|
with open(filename, "rb") as fid:
|
|
buffer = fid.read()
|
|
fileLen = len(buffer)
|
|
|
|
linelng = 0
|
|
for i in range(fileLen):
|
|
s = buffer[i:i+1]
|
|
c = byte2int(s)
|
|
if s == b'\n':
|
|
tmp = s
|
|
linelng = 0
|
|
elif c < 32 or c > 127 or s == b"'":
|
|
tmp = b"\\x%02x" % c
|
|
elif s == b"\\":
|
|
tmp = b"\\\\"
|
|
else:
|
|
tmp = s
|
|
|
|
if linelng > 70:
|
|
linelng = 0
|
|
outputList.append(b"\\\n")
|
|
|
|
outputList.append(tmp)
|
|
linelng += len(tmp)
|
|
|
|
return (b"".join(outputList)).decode('utf-8')
|
|
|
|
#-------------------------------------------------------------------
|
|
|
|
def NodeContainsFilename(self, node):
|
|
""" Does 'node' contain filename information at all? """
|
|
|
|
# Any bitmaps:
|
|
if node.nodeName == "bitmap":
|
|
return True
|
|
|
|
if node.nodeName == "icon":
|
|
return True
|
|
|
|
# URLs in wxHtmlWindow:
|
|
if node.nodeName == "url":
|
|
return True
|
|
|
|
# wxBitmapButton:
|
|
parent = node.parentNode
|
|
if parent.__class__ != minidom.Document and \
|
|
parent.getAttribute("class") == "wxBitmapButton" and \
|
|
(node.nodeName == "focus" or node.nodeName == "disabled" or
|
|
node.nodeName == "selected"):
|
|
return True
|
|
|
|
# wxBitmap or wxIcon toplevel resources:
|
|
if node.nodeName == "object":
|
|
klass = node.getAttribute("class")
|
|
if klass == "wxBitmap" or klass == "wxIcon":
|
|
return True
|
|
|
|
return False
|
|
|
|
#-------------------------------------------------------------------
|
|
|
|
def ReplaceFilenamesInXRC(self, node, files, resourcePath):
|
|
""" Finds all files mentioned in resource file, e.g. <bitmap>filename</bitmap>
|
|
and replaces them with the memory filenames.
|
|
|
|
Fills a list of the filenames found."""
|
|
|
|
# Is 'node' XML node element?
|
|
if node is None: return
|
|
if node.nodeType != minidom.Document.ELEMENT_NODE: return
|
|
|
|
containsFilename = self.NodeContainsFilename(node);
|
|
|
|
for n in node.childNodes:
|
|
|
|
if (containsFilename and
|
|
(n.nodeType == minidom.Document.TEXT_NODE or
|
|
n.nodeType == minidom.Document.CDATA_SECTION_NODE)):
|
|
|
|
filename = n.nodeValue
|
|
memoryFilename = self.GetMemoryFilename(filename)
|
|
n.nodeValue = memoryFilename
|
|
|
|
if filename not in files:
|
|
files.append(filename)
|
|
|
|
# Recurse into children
|
|
if n.nodeType == minidom.Document.ELEMENT_NODE:
|
|
self.ReplaceFilenamesInXRC(n, files, resourcePath);
|
|
|
|
#-------------------------------------------------------------------
|
|
|
|
def FindStringsInNode(self, parent):
|
|
def is_number(st):
|
|
try:
|
|
i = int(st)
|
|
return True
|
|
except ValueError:
|
|
return False
|
|
|
|
strings = []
|
|
if parent is None:
|
|
return strings;
|
|
|
|
for child in parent.childNodes:
|
|
if ((parent.nodeType == parent.ELEMENT_NODE) and
|
|
# parent is an element, i.e. has subnodes...
|
|
(child.nodeType == child.TEXT_NODE or
|
|
child.nodeType == child.CDATA_SECTION_NODE) and
|
|
# ...it is textnode...
|
|
(
|
|
parent.tagName == "label" or
|
|
(parent.tagName == "value" and
|
|
not is_number(child.nodeValue)) or
|
|
parent.tagName == "help" or
|
|
parent.tagName == "longhelp" or
|
|
parent.tagName == "tooltip" or
|
|
parent.tagName == "htmlcode" or
|
|
parent.tagName == "title" or
|
|
parent.tagName == "item"
|
|
)):
|
|
# ...and known to contain translatable string
|
|
if (parent.getAttribute("translate") != "0"):
|
|
strings.append(self.ConvertText(child.nodeValue))
|
|
|
|
# subnodes:
|
|
if child.nodeType == child.ELEMENT_NODE:
|
|
strings += self.FindStringsInNode(child)
|
|
|
|
return strings
|
|
|
|
#-------------------------------------------------------------------
|
|
|
|
def ConvertText(self, st):
|
|
r"""
|
|
Encode special characters as escaped C/Python string characters.
|
|
|
|
\n => \\n
|
|
\r => \\r
|
|
\t => \\t
|
|
\ => \\
|
|
" => \"
|
|
|
|
Returns result as string, which is bytes in py2 or unicode in py3.
|
|
"""
|
|
st2 = ""
|
|
dt = list(st)
|
|
|
|
skipNext = False
|
|
for i in range(len(dt)):
|
|
if skipNext:
|
|
skipNext = False
|
|
continue
|
|
|
|
if dt[i] == '_':
|
|
if dt[i+1] == '_':
|
|
st2 += '_'
|
|
skipNext = True
|
|
else:
|
|
st2 += '&'
|
|
elif dt[i] == '\n':
|
|
st2 += '\\n'
|
|
elif dt[i] == '\t':
|
|
st2 += '\\t'
|
|
elif dt[i] == '\r':
|
|
st2 += '\\r'
|
|
elif dt[i] == '\\':
|
|
if dt[i+1] not in ['n', 't', 'r']:
|
|
st2 += '\\\\'
|
|
else:
|
|
st2 += '\\'
|
|
elif dt[i] == '"':
|
|
st2 += '\\"'
|
|
else:
|
|
st2 += dt[i]
|
|
|
|
return st2
|
|
|
|
#-------------------------------------------------------------------
|
|
|
|
# Replace editable block contents with previous
|
|
def ReplaceBlocks(self, input):
|
|
output = []
|
|
block = None
|
|
blockLines = []
|
|
for l in input.split('\n'):
|
|
if not block:
|
|
mo = reBeginBlock.match(l)
|
|
if mo and mo.groups()[0] in self.blocks:
|
|
block = mo.groups()[0]
|
|
output.append(self.blocks[block])
|
|
else:
|
|
output.append(l + '\n')
|
|
else:
|
|
mo = reEndBlock.match(l)
|
|
if mo:
|
|
if mo.groups()[0] != block:
|
|
print("pywxrc: error: block mismatch: %s != %s" % (block, mo.groups()[0]))
|
|
block = None
|
|
return ''.join(output)
|
|
|
|
#-------------------------------------------------------------------
|
|
|
|
def _OpenOutputFile(self, outputFilename):
|
|
if outputFilename == "-":
|
|
outputFile = sys.stdout
|
|
else:
|
|
# Parse existing file to collect editable blocks
|
|
if os.path.isfile(outputFilename):
|
|
outputFile = open(outputFilename)
|
|
block = None
|
|
blockLines = []
|
|
for l in outputFile.readlines():
|
|
if not block:
|
|
mo = reBeginBlock.match(l)
|
|
if mo:
|
|
block = mo.groups()[0]
|
|
blockLines = [l]
|
|
else:
|
|
blockLines.append(l)
|
|
mo = reEndBlock.match(l)
|
|
if mo:
|
|
if mo.groups()[0] != block:
|
|
print("pywxrc: error: block mismatch: %s != %s" % (block, mo.groups()[0]))
|
|
self.blocks[block] = "".join(blockLines)
|
|
block = None
|
|
|
|
try:
|
|
outputFile = open(outputFilename, "wt")
|
|
except IOError:
|
|
raise IOError("Can't write output to '%s'" % outputFilename)
|
|
return outputFile
|
|
|
|
|
|
|
|
|
|
|
|
#---------------------------------------------------------------------------
|
|
|
|
def main(args=None):
|
|
if not args:
|
|
args = sys.argv[1:]
|
|
|
|
outputFilename = None
|
|
embedResources = False
|
|
generateGetText = False
|
|
assignVariables = True
|
|
generatePython = False
|
|
|
|
try:
|
|
opts, args = getopt.gnu_getopt(args,
|
|
"hpgevo:",
|
|
"help python gettext embed novar output=".split())
|
|
except getopt.GetoptError as exc:
|
|
print("\nError : %s\n" % str(exc))
|
|
print(__doc__)
|
|
sys.exit(1)
|
|
|
|
# If there is no input file argument, show help and exit
|
|
if not args:
|
|
print(__doc__)
|
|
print("No xrc input file was specified.")
|
|
sys.exit(1)
|
|
|
|
# Parse options and arguments
|
|
for opt, val in opts:
|
|
if opt in ["-h", "--help"]:
|
|
print(__doc__)
|
|
sys.exit(1)
|
|
|
|
if opt in ["-p", "--python"]:
|
|
generatePython = True
|
|
|
|
if opt in ["-o", "--output"]:
|
|
outputFilename = val
|
|
|
|
if opt in ["-e", "--embed"]:
|
|
embedResources = True
|
|
|
|
if opt in ["-v", "--novar"]:
|
|
assignVariables = False
|
|
|
|
if opt in ["-g", "--gettext"]:
|
|
generateGetText = True
|
|
|
|
|
|
# check for and expand any wildcards in the list of input files
|
|
inputFiles = []
|
|
for arg in args:
|
|
inputFiles += glob.glob(arg)
|
|
|
|
|
|
comp = XmlResourceCompiler()
|
|
|
|
try:
|
|
if generatePython:
|
|
if not outputFilename:
|
|
outputFilename = os.path.splitext(args[0])[0] + "_xrc.py"
|
|
comp.MakePythonModule(inputFiles, outputFilename,
|
|
embedResources, generateGetText,
|
|
assignVariables)
|
|
|
|
elif generateGetText:
|
|
if not outputFilename:
|
|
outputFilename = '-'
|
|
comp.MakeGetTextOutput(inputFiles, outputFilename)
|
|
|
|
else:
|
|
print(__doc__)
|
|
print("One or both of -p, -g must be specified.")
|
|
sys.exit(1)
|
|
|
|
|
|
except IOError as exc:
|
|
print_("%s." % str(exc), file=sys.stderr)
|
|
else:
|
|
if outputFilename != "-":
|
|
print_("Resources written to %s." % outputFilename, file=sys.stderr)
|
|
|
|
if __name__ == "__main__":
|
|
main(sys.argv[1:])
|