需求:
上线工作涉及到表结构更新时,由开发人员提交变更的sql语句由DBA操作,为了保障表结构变更与PDM同步并且自动化,写了python脚本,解析DDL语句更新PDM文件。
PDM是XML文件为脚本更新提供可能。
代码如下:
PDM解析操作脚本:
#!/usr/bin/python # -*- coding: utf-8 -*- # from xml.dom.minidom import parse import xml.dom.minidom class PDMHandler(object): TBL_ATTR_LIST = ["Code"] COL_ATTR_LIST = ["Code"] tableNodes = {} columnNodes = {} modelTablesNode = None ph = None filename = 'package.pdm' def __init__(self): self.init(); return def init(self): self.ph = PDMHandler.parse(self.filename) for pkg in PDMHandler.getPkgNodes(self.ph): for tbl in PDMHandler.getTblNodesInPkg(pkg): tbl_attrs = PDMHandler.getTblAttrs(tbl) self.tableNodes[tbl_attrs['Code']] = tbl for col in PDMHandler.getColNodesInTbl(tbl): col_attrs = PDMHandler.getColAttrs(col) self.columnNodes[tbl_attrs['Code'] + '_' + col_attrs['Code']] = col @staticmethod def parse(pdmfilename): """ @brief PDM文件导入函数 @param[in] pdmfilename pdm文件名 @return pdm文件的DOM结构 """ return xml.dom.minidom.parse(pdmfilename) @staticmethod def __get_nodes_by_path(parent, xml_path): """ @brief 按路径取节点核心函数,通过传入一个节点和一个路径 (类文件系统路径)的字符串,获得相应的子结点(或集合). 例1: __get_nodes_by_path(node,"a/b/c|3/d") 即获得node下a下b下第3个c节点下的所有d节点的一个 list. 例2: __get_nodes_by_path(node,"a/b/c|3/d|2") 即获得node下a下b下第3个c节点下的第2个d节点. 注意:此处返回的不是list,而是节点 @param[in] parent DOM中的父节点 @return 节点list或节点句柄 """ curr_node = parent for tag in xml_path.split("/")[0:-1]: tag_desc = tag.split("|") tag_name, tag_index = tag_desc[0], (int(tag_desc[1]) if len(tag_desc) == 2 else 0) child_nodes = [] for child_node in curr_node.childNodes: if child_node.nodeName == tag_name: child_nodes.append(child_node) if len(child_nodes) < tag_index + 1: return [] curr_node = child_nodes[tag_index] # -- 最后一级路径特殊处理 -- # tag = xml_path.split("/")[-1] tag_desc = tag.split("|") tag_name, tag_index = tag_desc[0], (int(tag_desc[1]) if len(tag_desc) == 2 else None) child_nodes = [] for child_node in curr_node.childNodes: if child_node.nodeName == tag_name: child_nodes.append(child_node) if tag_index == None: return child_nodes elif len(child_nodes) < tag_index + 1: return [] else: curr_node = child_nodes[tag_index] return curr_node @staticmethod def __get_attrs_by_list(parent, attr_list): """ @brief 取PDM属性(注意:此属性非xml属性) 背景:PDM文件中主要节点(o:Table,o:Column...)的具体属性 一般以子节点形式表示,而不以通常的XML属性表示.此 函数传入一个attr_list,输出一个字典表示的: { "attr":"value" ...} @param[in] parent 需要被取属性的节点句柄 @param[in] attr_list 一个字符串的list["attr1","attr2"...],代表要取的属性名集合 @return 返回一个dict:{"attr1":"value" ...} """ ret_dict = {} for attr in attr_list: ret_dict[attr] = "" for child in parent.childNodes: if child.nodeName == "a:" + attr: ret_dict[attr] = child.childNodes[0].data break return ret_dict @staticmethod def __get_pkgnodes_recursively(o_pkg): # -- 需要传入一个o:Model/o:Package节点 --# if o_pkg.nodeName != "o:Model" and o_pkg.nodeName != "o:Package": return [] ret_list = [] subpkgs = PDMHandler.__get_nodes_by_path(o_pkg, "c:Packages/o:Package") if subpkgs != None: for subpkg in subpkgs: ret_list.append(subpkg) ret_list = ret_list + PDMHandler.__get_pkgnodes_recursively(subpkg) else: return [] return ret_list @staticmethod def getPkgNodes(hpdm): """ @brief 获取pdm文件中所有的o:package的句柄list @param[in] hpdm 待处理的pdm文件DOM句柄(可通过PDMHandler.parse取得) @return 返回pacakge的节点list:[pkg1,pkg2...],元素为DOM节点类型 """ ret_list = [] try: o_mdl = PDMHandler.__get_nodes_by_path(hpdm, "Model/o:RootObject/c:Children/o:Model")[0] ret_list.append(o_mdl) except IndexError: print("ERROR:不是一个合法的pdm文件!") return [] ret_list = ret_list + PDMHandler.__get_pkgnodes_recursively(o_mdl) return ret_list @staticmethod def getModelTablesNodes(hpdm): """ @brief 获取pdm文件中所有的o:package的句柄list @param[in] hpdm 待处理的pdm文件DOM句柄(可通过PDMHandler.parse取得) @return 返回pacakge的节点list:[pkg1,pkg2...],元素为DOM节点类型 """ ret_list = [] try: o_mdl = PDMHandler.__get_nodes_by_path(hpdm, "Model/o:RootObject/c:Children/o:Model/c:Tables") if len(o_mdl) == 0: o_mdl = PDMHandler.__get_nodes_by_path(hpdm, "Model/o:RootObject/c:Children/o:Model")[0] cTables = hpdm.createElement('c:Tables') o_mdl.appendChild(cTables) ret_list.append(cTables) else: ret_list.append(o_mdl[0]) except IndexError: return [] return ret_list @staticmethod def getTblNodesInPkg(pkgnode): """ @brief 获取指定o:Package下的所有o:Table的list @param[in] pkgnode 待处理的o:Package节点(可通过PDMHandler.getPkgNodes取得) @return 返回o:Table的节点list:[tbl1,tbl2...],元素为DOM节点类型 """ return PDMHandler.__get_nodes_by_path(pkgnode, "c:Tables/o:Table") @staticmethod def getColNodesInTbl(tblnode): """ @brief 获取指定o:Table下的所有o:Column的list @param[in] tblnode 待处理的o:Table节点(可通过PDMHandler.getTblInPkg取得) @return 返回o:Column的节点list:[col1,col2...],元素为DOM节点类型 """ return PDMHandler.__get_nodes_by_path(tblnode, "c:Columns/o:Column") @staticmethod def getIdxNodesInTbl(tblnode): """ @brief 获取指定o:Table下的所有o:Index的list @param[in] tblnode 待处理的o:Table节点(可通过PDMHandler.getTblInPkg取得) @return 返回o:Index的节点list:[idx1,idx2...],元素为DOM节点类型 """ return PDMHandler.__get_nodes_by_path(tblnode, "c:Indexes/o:Index") @staticmethod def getIdxColNodesInIdx(idxnode): """ @brief 获取指定o:Index下的所有o:IndexColumn的list @param[in] idxnode 待处理的o:Index节点(可通过PDMHandler.getIdxNodesInTbl取得) @return 返回o:IndexColumn的节点list:[idxcol1,idxcol2...],元素为DOM节点类型 """ return PDMHandler.__get_nodes_by_path(idxnode, "c:IndexColumns/o:IndexColumn") @staticmethod def getTblAttrs(tblnode): """ @brief 获取指定o:Table的属性(可取的属性参见PDMHandler.TBL_ATTR_LIST) @param[in] tblnode 待处理的o:Table节点(可通过PDMHandler.getTblNodesInPkg取得) @return 返回一个字典dict:{"attr1":"value",...} """ return PDMHandler.__get_attrs_by_list(tblnode, PDMHandler.TBL_ATTR_LIST) @staticmethod def getColAttrs(colnode): """ @brief 获取指定o:Column的属性(可取的属性参见PDMHandler.COL_ATTR_LIST) @param[in] colnode 待处理的o:Column节点(可通过PDMHandler.getColNodesInTbl取得) @return 返回一个字典dict:{"attr1":"value",...} """ return PDMHandler.__get_attrs_by_list(colnode, PDMHandler.COL_ATTR_LIST) @staticmethod def getNodePath(node): """ @brief 获取指定node的路径(模拟文件系统路径表示法) @param[in] node 待处理的节点,类型为DOM的节点类型 @return 返回一个字符串表示node的路径,形如"/foo/bar/node" """ curr = node path_nodes = [] while (1): if curr != None and curr.nodeName != "#document": path_nodes.append(curr.tagName) else: break curr = curr.parentNode path_nodes.reverse() path = "".join([slash + node for slash in '/' for node in path_nodes]) return path @staticmethod def getNode(parent, nodeName): """ @brief 判断结点parent的名称是否与参数名称nodeName相同,相同返回parent,否则返则None :param parent: 节点 :param nodeName: 参数名称 :return: """ for node in parent.childNodes: if node.nodeName == 'a:Name' and node.childNodes[0].data == nodeName: return parent return None @staticmethod def getTableColumnsNode(tableNode): """ @brief 根据表结点tableNode查询列结点 :param tableNode: 表节点 :return: 表节点下节点c:Columns """ for node in tableNode.childNodes: if node.nodeName == 'c:Columns': return node return None @staticmethod def addColumn(pdm, tableCode, columnObjectList=None): """ @brief 增加列 :param pdm: dom文档 :return: """ if tableCode == None: print('待增加列对应表编码不能为空') return False if columnObjectList == None or len(columnObjectList) == 0: print('待增加列不能为空且格式为[{"code":,"name":,...}]') return False for columnObject in columnObjectList: tableNode = pdm.tableNodes[tableCode] TableColumnsNode = PDMHandler.getTableColumnsNode(tableNode) nColumn = pdm.ph.createElement('o:Column') TableColumnsNode.appendChild(nColumn) nCode = pdm.ph.createElement('a:Code') cCode = pdm.ph.createTextNode(columnObject['code']) nCode.appendChild(cCode) nColumn.appendChild(nCode) pdm.columnNodes[tableCode + '_' + columnObject['code']] = nColumn nName = pdm.ph.createElement('a:Name') cName = pdm.ph.createTextNode(columnObject['code'] if columnObject.get('name') == None else columnObject['name']) nName.appendChild(cName) nColumn.appendChild(nName) if columnObject.get('comment') != None: nComment = pdm.ph.createElement('a:Comment') cComment = pdm.ph.createTextNode(columnObject['comment']) nComment.appendChild(cComment) nColumn.appendChild(nComment) nDataType = pdm.ph.createElement('a:DataType') cDataType = pdm.ph.createTextNode(columnObject['dataType']) nDataType.appendChild(cDataType) nColumn.appendChild(nDataType) nLength = pdm.ph.createElement('a:Length') cLength = pdm.ph.createTextNode(columnObject['length']) nLength.appendChild(cLength) nColumn.appendChild(nLength) @staticmethod def updateColumn(pdm, tableCode, columnObjectList=None): """ @breif 更新列 :param pdm: dom文档 :param tableCode: 表编码 :param columnCode: :return: """ if tableCode == None: print('待修改列对应表编码不能为空') return False if columnObjectList == None or len(columnObjectList) == 0: print('待增加列不能为空且格式为[{"code":,"name":},...]') return False for columnObject in columnObjectList: columnNode = pdm.columnNodes[tableCode + '_' + columnObject['code']] for key in columnObject.keys(): if key != 'code': tf = False for node in columnNode.childNodes: if node.nodeName == 'a:' + key.capitalize(): node.removeChild(node.childNodes[0]) cName = pdm.ph.createTextNode(columnObject[key]) node.appendChild(cName) tf = True break if tf == False: csNode = pdm.ph.createElement('a:' + key.capitalize()) columnNode.appendChild(csNode) cName = pdm.ph.createTextNode(columnObject[key]) csNode.appendChild(cName) @staticmethod def addTable(pdm, tableObject=None): """ @breif 增加表 :param pdm: dom文档 :return: """ if tableObject == None or tableObject.get('code') == None: print('待增加表不能为空且格式为{"code":,...}') return False if pdm.modelTablesNode == None: pdm.modelTablesNode = PDMHandler.getModelTablesNodes(pdm.ph)[0] nTable = pdm.ph.createElement('o:Table') pdm.modelTablesNode.appendChild(nTable) pdm.tableNodes[tableObject['code']] = nTable nCode = pdm.ph.createElement('a:Code') cCode = pdm.ph.createTextNode(tableObject['code']) nCode.appendChild(cCode) nTable.appendChild(nCode) # tableObject['name'] = tableObject['code'] if tableObject.get('name') != None else tableObject['name'] # if tableObject.get('name') != None: nName = pdm.ph.createElement('a:Name') cName = pdm.ph.createTextNode(tableObject['code'] if tableObject.get('name') == None else tableObject['name']) nName.appendChild(cName) nTable.appendChild(nName) if tableObject.get('comment') != None: nComment = pdm.ph.createElement('a:Comment') cComment = pdm.ph.createTextNode(tableObject['comment']) nComment.appendChild(cComment) nTable.appendChild(nComment) nColumns = pdm.ph.createElement('c:Columns') nTable.appendChild(nColumns) @staticmethod def updateTable(pdm, tableCode,tableObject=None): """ @breif 增加表 :param pdm: dom文档 :return: """ if tableObject == None: print('待修改表不能为空且格式为{"name":,....}') return False tableNode = pdm.tableNodes[tableCode] for key in tableObject.keys(): if key != 'code': tf = False for node in tableNode.childNodes: if node.nodeName == 'a:' + key.capitalize(): node.removeChild(node.childNodes[0]) cName = pdm.ph.createTextNode(tableObject[key]) node.appendChild(cName) tf = True break if tf == False: tsNode = pdm.ph.createElement('a:' + key.capitalize()) tableNode.appendChild(tsNode) cName = pdm.ph.createTextNode(tableObject[key]) tsNode.appendChild(cName) @staticmethod def delTable(pdm, tableCodeList=None): """ @breif 删除表 :param pdm: dom文档 :return: """ if tableCodeList == None or len(tableCodeList) == 0: print('待删除表code列表不能为空') return False for tableCode in tableCodeList: tableNode = pdm.tableNodes[tableCode] tableNode.parentNode.removeChild(tableNode) pdm.tableNodes.pop(tableCode) keysTmp = [] for key in pdm.columnNodes.keys(): keysTmp.append(key) for key in keysTmp: if key.startswith(tableCode + '_'): pdm.columnNodes.pop(key) @staticmethod def delColumn(pdm,tableCode, columnCodeList=None): """ @breif 删除列 :param pdm: dom文档 :return: """ if columnCodeList == None or len(columnCodeList) == 0: print("待删除表code列表不能为空且格式如下['columnCode'...]") return False for columnCode in columnCodeList: colunmNode = pdm.columnNodes[tableCode + '_' + columnCode] colunmNode.parentNode.removeChild(colunmNode) pdm.columnNodes.pop(tableCode + '_' + columnCode)
DDL解析操作脚本:
#!/usr/bin/python # -*- coding: utf-8 -*- # import re class DDLHandler(object): """ @preif DDL解析 """ ANNOTION = ['-','#'] LIST_DDL = ['CREATE TABLE','DROP TABLE','ALTER TABLE','COMMENT ON TABLE','COMMENT ON COLUMN'] list_ddl = [] list_ddl_line_join = [] fileName = 'ddl.sql' list_table_add = [] list_column_add = {} list_table_del = [] list_table_update = {} list_column_update = {} list_column_del = {} def __init__(self): self.init() self.joinLine() self.parseDdl() return def init(self): with open(self.fileName,'r',encoding='utf-8') as fl: for line in fl: if not re.match('^\s*('+'|'.join(self.ANNOTION)+')',line): line = re.sub('\s+',' ', line) self.list_ddl.append(line.upper().strip()) def joinLine(self): list_join = [] for line in self.list_ddl: if re.match('^\s*('+'|'.join(self.LIST_DDL)+')', line): if re.match('^\s*' + self.LIST_DDL[0], line): if re.match('.*\);\s*$', line): self.list_ddl_line_join.append(line) else: list_join.append(line) else: self.list_ddl_line_join.append(line) elif re.match('^\s*\);\s*$', line): list_join.append(line) self.list_ddl_line_join.append(' '.join(list_join)) list_join = [] else: list_join.append(line) def parseDdl(self): for sql in self.list_ddl_line_join: if re.match('^\s*'+self.LIST_DDL[0], sql): self.parseTableCreate(sql) elif re.match('^\s*'+self.LIST_DDL[1], sql): self.parseTableDel(sql) elif re.match('^\s*'+self.LIST_DDL[2], sql): self.parseColumnUpdate(sql) elif re.match('^\s*'+self.LIST_DDL[3], sql): self.parseTableComment(sql) elif re.match('^\s*'+self.LIST_DDL[4], sql): self.parseColumnComment(sql) def parseTableCreate(self,sql): regex = re.compile('^\s*CREATE\s+TABLE\s+(?P<tableCode>\w+)\s*\( (?P<columnCodes>.+)\);') match = regex.match(sql) tableCode = match.groupdict()['tableCode'] columnCodes = match.groupdict()['columnCodes'] columnCodes = columnCodes.split(',') tableObject = {'code':tableCode} self.list_table_add.append(tableObject) list = [] for column in columnCodes: regex = re.compile('^\s*(?P<columnCode>\w+)\s+(?P<dataType>\w+)(\((?P<length>\d+)\))?.*$') match = regex.match(column) length = match.groupdict()['length'] if match.groupdict().get('length') != None else '' dataType = match.groupdict()['dataType']+ ('('+match.groupdict()['length']+')' if match.groupdict().get('length') != None else '') data = {'code':match.groupdict()['columnCode'],'dataType':dataType,'length':length} list.append(data) self.list_column_add[tableCode] = list def parseTableDel(self, sql): regex = re.compile('^\s*DROP\s+TABLE\s+(?P<tableCode>\w+)\s*;') match = regex.match(sql) self.list_table_del.append(match.groupdict()['tableCode']) def parseColumnUpdate(self, sql): if re.match('^\s*ALTER\s+TABLE\s+\w+\s+ADD', sql): regex = re.compile('^\s*ALTER\s+TABLE\s+(?P<tableCode>\w+)\s+ADD\s*\((?P<columnCode>\w+)\s+(?P<dataType>\w+)(\((?P<length>\d+)\))?\);$') match = regex.match(sql) length = match.groupdict()['length'] if match.groupdict().get('length') != None else '' dataType = match.groupdict()['dataType'] + ('(' + match.groupdict()['length'] + ')' if match.groupdict().get('length') != None else '') data = {'code':match.groupdict()['columnCode'],'dataType':dataType,'length':length} if self.list_column_add.get(match.groupdict()['tableCode']) == None: self.list_column_add[match.groupdict()['tableCode']] = [data] else: self.list_column_add[match.groupdict()['tableCode']].append(data) elif re.match('^\s*ALTER\s+TABLE\s+\w+\s+DROP', sql): regex = re.compile('^\s*ALTER\s+TABLE\s+(?P<tableCode>\w+)\s+DROP\s+COLUMN\s+(?P<columnCode>\w+);$') match = regex.match(sql) if self.list_column_del.get(match.groupdict()['tableCode']) == None: self.list_column_del[match.groupdict()['tableCode']] = [match.groupdict()['columnCode']] else: self.list_column_del[match.groupdict()['tableCode']].append(match.groupdict()['columnCode']) elif re.match('^\s*ALTER\s+TABLE\s+\w+\s+MODIFY', sql): regex = re.compile('^\s*ALTER\s+TABLE\s+(?P<tableCode>\w+)\s+MODIFY\s*\((?P<columnCode>\w+)\s+(?P<dataType>\w+)(\((?P<length>\d+)\))?\);$') match = regex.match(sql) length = match.groupdict()['length'] if match.groupdict().get('length') != None else '' dataType = match.groupdict()['dataType'] + ('(' + match.groupdict()['length'] + ')' if match.groupdict().get('length') != None else '') data = {'code': match.groupdict()['columnCode'], 'dataType': dataType, 'length': length} if self.list_column_update.get(match.groupdict()['tableCode']) == None: self.list_column_update[match.groupdict()['tableCode']] = [data] else: self.list_column_update[match.groupdict()['tableCode']].append(data) def parseTableComment(self,sql): regex = re.compile("^\s*COMMENT\s+ON\s+TABLE\s+(?P<tableCode>\w+)\s+IS\s+'(?P<comment>.*)';$") match = regex.match(sql) if self.list_table_update.get(match.groupdict()['tableCode']) == None: self.list_table_update[match.groupdict()['tableCode']] = {'comment':match.groupdict()['comment']} else: self.list_table_update[match.groupdict()['tableCode']]['comment'] = match.groupdict()['comment'] def parseColumnComment(self,sql): regex = re.compile("^\s*COMMENT\s+ON\s+COLUMN\s+(?P<tableCode>\w+)\.(?P<columnCode>\w+)\s+IS\s+'(?P<comment>.*)';$") match = regex.match(sql) data = {'code': match.groupdict()['columnCode'], 'comment': match.groupdict()['comment']} if self.list_column_update.get(match.groupdict()['tableCode']) == None: self.list_column_update[match.groupdict()['tableCode']] = [data] else: self.list_column_update[match.groupdict()['tableCode']].append(data) if __name__ == '__main__': # ddl = DDLHandler() # text = 'CREATE TABLE STUDENTS( SNO CHAR(10), SNAME VARCHAR2(20), AGE NUMBER(3), SCHOOLDATE DATE, GRADE NUMBER(1) DEFAULT 1, CLAZZ NUMBER(2), EMAIL VARCHAR2(100) );' # ddl.parseTableCreate(text) # print(ddl.list_column_add) # text = "COMMENT ON COLUMN BANNER.NAME IS 'COMMENT';" # regex = re.compile("^\s*COMMENT\s+ON\s+COLUMN\s+(?P<tableCode>\w+)\.(?P<columnCode>\w+)\s+IS\s+'(?P<comment>.*)';$") # match = regex.match(text) # print(match.groupdict()) text = 'CREATE TABLE NOTE' print(re.sub('\s+',' ',text)) # if re.match('^\s*' + 'CREATE TABLE', text.replace('\s+',' ')): # print(True) # else: # print(False)DDL文件:--创建表结构 CREATE TABLE STUDENT( SNO CHAR(10), SNAME VARCHAR2(20), AGE NUMBER(3), SCHOOLDATE DATE, GRADE NUMBER(1) DEFAULT 1, CLAZZ NUMBER(2), EMAIL VARCHAR2(100) ); CREATE TABLE STUDENTS( SNO CHAR(10), SNAME VARCHAR2(20), AGE NUMBER(3), SCHOOLDATE DATE, GRADE NUMBER(1) DEFAULT 1, CLAZZ NUMBER(2), EMAIL VARCHAR2(100) ); --删除表结构 DROP TABLE STUDENT; DROP TABLE STUDENTS; --修改表的名称 RENAME BANK TO SXTBANK; --向表中添加注释 COMMENT ON TABLE STUDENT is 'banner'; -------------------修改表的结构 --添加列 ALTER TABLE STUDENT ADD(ADDRESS VARCHAR2(100)); ALTER TABLE STUDENT ADD(CLASS VARCHAR2(100)); --删除列 ALTER TABLE STUDENT DROP COLUMN ADDRESS; ALTER TABLE STUDENT DROP COLUMN AGE; ALTER TABLE STUDENTS DROP COLUMN SCHOOLDATE; --修改列 ALTER TABLE STUDENT MODIFY(EMAIL VARCHAR2(200)); ALTER TABLE STUDENT MODIFY(CLASS VARCHAR2(200)); --向列添加注释 COMMENT ON COLUMN STUDENT.sname is 'the name of employees';测试脚本:from PDMHandler import PDMHandler from DDLHandler import DDLHandler if __name__ == '__main__': ddl = DDLHandler() pmd = PDMHandler() #创建表 if len(ddl.list_table_add) != 0: for sql_table_add in ddl.list_table_add: PDMHandler.addTable(pmd, sql_table_add) #修改表 if len(ddl.list_table_update) != 0: for key in ddl.list_table_update.keys(): PDMHandler.updateTable(pmd, key, ddl.list_table_update[key]) #增加列 if len(ddl.list_column_add) != 0: for key in ddl.list_column_add.keys(): PDMHandler.addColumn(pmd, key, ddl.list_column_add[key]) #修改列 if len(ddl.list_column_update) != 0: for key in ddl.list_column_update.keys(): PDMHandler.updateColumn(pmd, key, ddl.list_column_update[key]) #删除列 if len(ddl.list_column_del) != 0: for key in ddl.list_column_del.keys(): PDMHandler.delColumn(pmd, key, ddl.list_column_del[key]) #删除表 if len(ddl.list_table_del) != 0: PDMHandler.delTable(pmd, ddl.list_table_del) #写入文件 with open('package_tmp.pdm', 'w',encoding = 'utf-8') as xmlfile: pmd.ph.writexml(xmlfile, addindent=' ' * 2, newl='\n', encoding='utf-8')测试通过,待改进