1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
|
#!/usr/bin/env python2.7
# This file is part of KDevelop
# SPDX-FileCopyrightText: 2011 Victor Varvariuc <victor.varvariuc@gmail.com>
# SPDX-FileCopyrightText: 2011 Sven Brauch <svenbrauch@googlemail.com>
import sys
from xml.dom import minidom
class NoDefaultValue():
'''Unique object for marking absence of a default value for a function argument.'''
def indentCode(code, level):
'''Indent given source code.'''
return '\n'.join(' ' * level + line for line in code.splitlines())
def getNodeNames(node):
'''Return node name. Chop the beginning of the name if it starts with parent's prefix:
QWidget.RenderFlags.__init__ -> __init__'''
name = node.attributes['name'].value
parentNode = node.parentNode
if parentNode.nodeName == 'Enum': # for unem members name prefix is not enum's name, but its parent's
parentNode = parentNode.parentNode
parentName = []
while parentNode.nodeName == 'Class':
parentName.append(parentNode.attributes['name'].value)
parentNode = parentNode.parentNode
parentName = list(reversed(parentName))
parentName = '.'.join(parentName) + '.'
if name.startswith(parentName):
return name[len(parentName):], name
return name, name
def parseEnum(enumNode):
'''Parse Enum node and return its source code.'''
enumMembers = []
enumName, enumFullName = getNodeNames(enumNode)
for node in enumNode.childNodes:
if node.nodeType == node.ELEMENT_NODE:
if node.nodeName == 'EnumMember':
#print getNodeNames(node)
enumMemberName = getNodeNames(node)[0]
if enumMemberName == 'None':
enumMemberName = '__kdevpythondocumentation_builtin_None'
enumMembers.append(enumMemberName)
else:
print 'Unknown sub-node in Enum %s: %s' % (enumFullName, node.nodeName)
text = '# Enum %s\n' % enumFullName
for enumMember in enumMembers:
text += '%s = 0\n' % enumMember
return text
def parseFunction(functionNode):
'''Parse Function node and return its code.'''
params = []
retType = 'void'
funcName, funcFullName = getNodeNames(functionNode)
for node in functionNode.childNodes:
if node.nodeType == node.ELEMENT_NODE:
if not node.hasAttribute('typename'):
node.attributes['typename'] = 'unknown'
if node.nodeName == 'Argument':
argType = node.attributes['typename'].value
try:
argName = '*args' if argType == '...' else node.attributes['name'].value
except KeyError: # no `name` attribute - it's function return value type
retType = argType
else:
try: # some arguments have default values
defaultValue = node.attributes['default'].value
except KeyError:
defaultValue = NoDefaultValue
try:
defaultValue = defaultValue.replace("QString", "str")
defaultValue = defaultValue.replace("QChar", "bytes")
except AttributeError:
pass
params.append((argType, argName, defaultValue))
else:
print 'Unhandled sub-node type in function %s: %s' % (funcFullName, node.nodeName)
descr = 'abstract ' if 'abstract' in functionNode.attributes.keys() else ''
descr += 'static ' if 'static' in functionNode.attributes.keys() else ''
namesUsed = set(['self', 'exec', 'print', 'from', 'in', 'def', 'if', 'for',
'while', 'return', 'raise', 'pass', 'global', 'del']) # reserved words which cannot be used as argument names
if funcFullName in namesUsed:
funcFullName += '_'
# function parameters in description
paramsStr = []
for p in params:
paramsStr.append('{} {}'.format(*p) if p[2] is NoDefaultValue else '{} {} = {}'.format(*p))
descr += '%s %s(%s)' % (retType, funcFullName, ', '.join(paramsStr))
# function parameters in function definition
paramsStr = ['self'] if functionNode.parentNode.nodeName == 'Class' else [] # add `self` first parameter for methods
hadDefault = False # there has been a default argument previously
for _, argName, defaultValue in params:
while argName in namesUsed: # some function arguments have same names...
print 'Adjusting arg name: `%s` in `%s`' % (argName, funcFullName) # http://www.riverbankcomputing.co.uk/static/Docs/PyQt4/html/qdatetime.html#QDateTime-5
argName = argName + '_'
namesUsed.add(argName)
if defaultValue is NoDefaultValue and not hadDefault:
paramsStr.append(argName)
else:
if defaultValue is NoDefaultValue or not defaultValue.replace('(','').replace(')','').isalnum():
defaultValue = "None"
paramsStr.append('{} = {}'.format(argName, defaultValue))
hadDefault = True
text = "def %s(%s):\n '''%s'''\n" % (funcName, ', '.join(paramsStr), descr)
if retType == 'void':
return text # do not print `return None` statement
if retType.startswith('list-of-'):
retType = '[' + retType[8:] + '()]'
elif retType.find("-or-") != -1:
a, b = retType.split("-or-")
retType = "{0}() if True else {1}()".format(a, b)
elif retType.startswith("dict-of-"):
q = retType[8:].split('-')
key, value = q[0], q[1]
retType = '{' + key + "():" + value + "()}"
else:
retType += '()'
retType = retType.replace("QString", "str")
retType = retType.replace("QChar", "bytes")
retType = retType.replace("const", "")
return text + ' return %s' % retType
def parseClass(classNode):
'''Parse Class node and return its api python code.'''
className, classFullName = getNodeNames(classNode)
try:
parentClasses = classNode.attributes['inherits'].value.split()
except KeyError:
parentClasses = []
text = 'class %s(%s):\n """"""\n' % (className, ', '.join(parentClasses))
for node in classNode.childNodes:
if node.nodeType == node.ELEMENT_NODE:
if node.nodeName == 'Member':
text += ' %s = None # %s - member\n' % (getNodeNames(node)[0], node.attributes['typename'].value)
elif node.nodeName == 'Function':
name = getNodeNames(node)[0]
if name not in ('exec', 'print'): # skip these invalid for Python names
text += indentCode(parseFunction(node), 1) + '\n'
elif node.nodeName == 'Enum':
text += indentCode(parseEnum(node), 1) + '\n\n'
elif node.nodeName == 'Class':
text += indentCode(parseClass(node), 1) + '\n'
elif node.nodeName == 'Signal':
text += ' %s = pyqtSignal() # %s - signal\n' % (getNodeNames(node)[0], node.attributes['sig'].value)
else:
print 'Unhandled sub-node type in class %s: %s' % (classFullName, node.nodeName)
return text
def convertXmlToPy(inFilePath):
print '\nConverting .xml PyQt5 module (%s) to .py' % inFilePath
print 'Parsing xml...'
dom = minidom.parse(inFilePath)
module = dom.firstChild
assert module.nodeName == 'Module'
moduleName = module.attributes['name'].value
print 'Module name:', moduleName
outFilePath = moduleName + '.py'
stats = {}
with open(outFilePath, 'w') as file:
file.write('class pyqtSignal():\n def connect(self, targetSignal): pass\n def emit(self, *args): pass\n')
file.write('from QtCore import *\n\n')
file.write('from QtWidgets import *\n\n')
file.write('import datetime\n\n')
for node in module.childNodes:
if node.nodeType != node.ELEMENT_NODE:
continue # skip non element nodes
nodeName = node.nodeName
stats[nodeName] = stats.setdefault(nodeName, 0) + 1 # stats
if nodeName == 'Class':
file.write(parseClass(node) + '\n\n')
elif nodeName == 'Function':
if not 'extends' in node.attributes.keys(): # skip module level functions, which extend unpresent here class
file.write(parseFunction(node) + '\n\n')
elif nodeName == 'Member':
file.write('%s = None # %s member\n\n' % (getNodeNames(node)[0], node.attributes['typename'].value))
elif nodeName == 'Enum':
file.write(parseEnum(node) + '\n\n')
else:
print 'Unhandled node type in the module:', nodeName
print 'Stats:', stats
files = sys.argv[1:] # ['QtGui', 'QtCore'] # modules to convert
for fileName in files:
convertXmlToPy(fileName)
|