File indexing completed on 2024-06-09 04:34:04

0001 #!/usr/bin/env python2.7
0002 # This file is part of KDevelop
0003 # SPDX-FileCopyrightText: 2011 Victor Varvariuc <victor.varvariuc@gmail.com>
0004 # SPDX-FileCopyrightText: 2011 Sven Brauch <svenbrauch@googlemail.com>
0005 
0006 import sys
0007 from xml.dom import minidom
0008 
0009 
0010 class NoDefaultValue():
0011     '''Unique object for marking absence of a default value for a function argument.'''
0012 
0013 def indentCode(code, level):
0014     '''Indent given source code.'''
0015     return '\n'.join('    ' * level + line for line in code.splitlines())
0016 
0017 def getNodeNames(node):
0018     '''Return node name. Chop the beginning of the name if it starts with parent's prefix:
0019     QWidget.RenderFlags.__init__ -> __init__'''
0020     name = node.attributes['name'].value
0021     parentNode = node.parentNode
0022     if parentNode.nodeName == 'Enum': # for unem members name prefix is not enum's name, but its parent's
0023        parentNode = parentNode.parentNode
0024     
0025     parentName = []
0026     while parentNode.nodeName == 'Class':
0027         parentName.append(parentNode.attributes['name'].value)
0028         parentNode = parentNode.parentNode
0029     parentName = list(reversed(parentName))
0030     parentName = '.'.join(parentName) + '.'
0031     
0032     if name.startswith(parentName):
0033         return name[len(parentName):], name
0034     return name, name
0035 
0036 
0037 def parseEnum(enumNode):
0038     '''Parse Enum node and return its source code.'''
0039     enumMembers = []
0040     enumName, enumFullName = getNodeNames(enumNode)
0041     for node in enumNode.childNodes:
0042         if node.nodeType == node.ELEMENT_NODE:
0043             if node.nodeName == 'EnumMember':
0044                 #print getNodeNames(node)
0045                 enumMemberName = getNodeNames(node)[0]
0046                 if enumMemberName == 'None':
0047                     enumMemberName = '__kdevpythondocumentation_builtin_None'
0048                 enumMembers.append(enumMemberName)
0049             else:
0050                 print 'Unknown sub-node in Enum %s: %s' % (enumFullName, node.nodeName)
0051     text = '# Enum %s\n' % enumFullName
0052     for enumMember in enumMembers:
0053         text += '%s = 0\n' % enumMember
0054     return text
0055 
0056 
0057 def parseFunction(functionNode):
0058     '''Parse Function node and return its code.'''
0059     params = []
0060     retType = 'void'
0061     funcName, funcFullName = getNodeNames(functionNode)
0062     for node in functionNode.childNodes:
0063         if node.nodeType == node.ELEMENT_NODE:
0064             if not node.hasAttribute('typename'):
0065                 node.attributes['typename'] = 'unknown'
0066             if node.nodeName == 'Argument':
0067                 argType = node.attributes['typename'].value
0068                 try:
0069                     argName = '*args' if argType == '...' else node.attributes['name'].value
0070                 except KeyError: # no `name` attribute - it's function return value type
0071                     retType = argType
0072                 else:
0073                     try: # some arguments have default values
0074                         defaultValue = node.attributes['default'].value
0075                     except KeyError:
0076                         defaultValue = NoDefaultValue
0077                     try:
0078                         defaultValue = defaultValue.replace("QString", "str")
0079                         defaultValue = defaultValue.replace("QChar", "bytes")
0080                     except AttributeError:
0081                         pass
0082                     params.append((argType, argName, defaultValue))
0083             else:
0084                 print 'Unhandled sub-node type in function %s: %s' % (funcFullName, node.nodeName)
0085 
0086     descr = 'abstract ' if 'abstract' in functionNode.attributes.keys() else ''
0087     descr += 'static ' if 'static' in functionNode.attributes.keys() else ''
0088 
0089     namesUsed = set(['self', 'exec', 'print', 'from', 'in', 'def', 'if', 'for',
0090                      'while', 'return', 'raise', 'pass', 'global', 'del']) # reserved words which cannot be used as argument names
0091     if funcFullName in namesUsed:
0092         funcFullName += '_'
0093     
0094     # function parameters in description
0095     paramsStr = []
0096     for p in params:
0097         paramsStr.append('{} {}'.format(*p) if p[2] is NoDefaultValue else '{} {} = {}'.format(*p))
0098     descr += '%s %s(%s)' % (retType, funcFullName, ', '.join(paramsStr))
0099 
0100     # function parameters in function definition
0101     paramsStr = ['self'] if functionNode.parentNode.nodeName == 'Class' else [] # add `self` first parameter for methods
0102     hadDefault = False # there has been a default argument previously
0103     for _, argName, defaultValue in params:
0104         while argName in namesUsed: # some function arguments have same names...
0105             print 'Adjusting arg name: `%s` in `%s`' % (argName, funcFullName) # http://www.riverbankcomputing.co.uk/static/Docs/PyQt4/html/qdatetime.html#QDateTime-5
0106             argName = argName + '_'
0107         namesUsed.add(argName)
0108         if defaultValue is NoDefaultValue and not hadDefault:
0109             paramsStr.append(argName)
0110         else:
0111             if defaultValue is NoDefaultValue or not defaultValue.replace('(','').replace(')','').isalnum():
0112                 defaultValue = "None"
0113             paramsStr.append('{} = {}'.format(argName, defaultValue))
0114             hadDefault = True
0115 
0116     text = "def %s(%s):\n    '''%s'''\n" % (funcName, ', '.join(paramsStr), descr)
0117     if retType == 'void':
0118         return text # do not print `return None` statement
0119     if retType.startswith('list-of-'):
0120         retType = '[' + retType[8:] + '()]'
0121     elif retType.find("-or-") != -1:
0122         a, b = retType.split("-or-")
0123         retType = "{0}() if True else {1}()".format(a, b)
0124     elif retType.startswith("dict-of-"):
0125         q = retType[8:].split('-')
0126         key, value = q[0], q[1]
0127         retType = '{' + key + "():" + value + "()}"
0128     else:
0129         retType += '()'
0130     retType = retType.replace("QString", "str")
0131     retType = retType.replace("QChar", "bytes")
0132     retType = retType.replace("const", "")
0133 
0134     return text + '    return %s' % retType
0135 
0136 
0137 def parseClass(classNode):
0138     '''Parse Class node and return its api python code.'''
0139     className, classFullName = getNodeNames(classNode)
0140     try:
0141         parentClasses = classNode.attributes['inherits'].value.split()
0142     except KeyError:
0143         parentClasses = []
0144     text = 'class %s(%s):\n    """"""\n' % (className, ', '.join(parentClasses))
0145     for node in classNode.childNodes:
0146         if node.nodeType == node.ELEMENT_NODE:
0147             if node.nodeName == 'Member':
0148                 text += '    %s = None # %s - member\n' % (getNodeNames(node)[0], node.attributes['typename'].value)
0149             elif node.nodeName == 'Function':
0150                 name = getNodeNames(node)[0]
0151                 if name not in ('exec', 'print'): # skip these invalid for Python names
0152                     text += indentCode(parseFunction(node), 1) + '\n'
0153             elif node.nodeName == 'Enum':
0154                 text += indentCode(parseEnum(node), 1) + '\n\n'
0155             elif node.nodeName == 'Class':
0156                 text += indentCode(parseClass(node), 1) + '\n'
0157             elif node.nodeName == 'Signal':
0158                 text += '    %s = pyqtSignal() # %s - signal\n' % (getNodeNames(node)[0], node.attributes['sig'].value)
0159             else:
0160                 print 'Unhandled sub-node type in class %s: %s' % (classFullName, node.nodeName)
0161     return text
0162 
0163 
0164 def convertXmlToPy(inFilePath):
0165     print '\nConverting .xml PyQt5 module (%s) to .py' % inFilePath
0166 
0167     print 'Parsing xml...'
0168     dom = minidom.parse(inFilePath)
0169 
0170     module = dom.firstChild
0171     assert module.nodeName == 'Module'
0172     moduleName = module.attributes['name'].value
0173     print 'Module name:', moduleName
0174 
0175     outFilePath = moduleName + '.py'
0176     stats = {}
0177 
0178     with open(outFilePath, 'w') as file:
0179         file.write('class pyqtSignal():\n def connect(self, targetSignal): pass\n def emit(self, *args): pass\n')
0180         file.write('from QtCore import *\n\n')
0181         file.write('from QtWidgets import *\n\n')
0182         file.write('import datetime\n\n')
0183         for node in module.childNodes:
0184             if node.nodeType != node.ELEMENT_NODE:
0185                 continue # skip non element nodes
0186             nodeName = node.nodeName
0187             stats[nodeName] = stats.setdefault(nodeName, 0) + 1 # stats
0188             if nodeName == 'Class':
0189                 file.write(parseClass(node) + '\n\n')
0190             elif nodeName == 'Function':
0191                 if not 'extends' in node.attributes.keys(): # skip module level functions, which extend unpresent here class
0192                     file.write(parseFunction(node) + '\n\n')
0193             elif nodeName == 'Member':
0194                 file.write('%s = None # %s member\n\n' % (getNodeNames(node)[0], node.attributes['typename'].value))
0195             elif nodeName == 'Enum':
0196                 file.write(parseEnum(node) + '\n\n')
0197             else:
0198                 print 'Unhandled node type in the module:', nodeName
0199 
0200     print 'Stats:', stats
0201 
0202 
0203 files = sys.argv[1:] # ['QtGui', 'QtCore'] # modules to convert
0204 
0205 for fileName in files:
0206     convertXmlToPy(fileName)