Написание инструмента Python для преобразования XML в Python?
Меня попросили рассмотреть возможность преобразования XML в Python, чтобы можно было постепенно исключить XML из текущего процесса и заменить его новыми сценариями Python.
В настоящее время файлы XML используются python & ASP и выглядят по существу так;
<?xml version="1.0" encoding="UTF-8"?>
<script>
<stage id="stage1" nextStage="stage2">
<initialise>
<variable id="year" value="2012" type="Integer"/>
<executeMethod class="engineclass_date" method="getTodayAsString">
<arguments>
<variable id="someVar"/>
</arguments>
<return>
<variable id="todaysDate"/>
</return>
</executeMethod>
Благодаря pepr у меня нет парсера / компилятора в работах, которые производят код Python из XML. Требуется еще много работы, чтобы обработать все возможные элементы, но это работает, и, надеюсь, другие смогут извлечь из этого пользу, как и я!
Еще нужно найти способ написать отдельный файл для каждого этапа сценария, чтобы отступ работал правильно и правильно обрабатывал ошибки.
2 ответа
Используйте стандартное дерево xml.etree.Element для извлечения информации из XML в объекты Python (или более расширенный lxml третьей стороны с тем же API).
Я рекомендую прочитать " Погружение Марка Пилрима в Python 3", Глава 12. XML ( http://getpython3.com/diveintopython3/xml.html).
Вот ядро того, как парсер / компилятор может быть написан. Идея состоит в том, чтобы рекурсивно просматривать элементы, собирать необходимую информацию и выводить код, когда это возможно:
import xml.etree.ElementTree as ET
class Parser:
def __init__(self):
self.output_list = [] # collected output lines
self.il = 0 # indentation level
def __iter__(self):
return iter(self.output_list)
def out(self, s):
'''Output the indented string to the output list.'''
self.output_list.append(' ' * self.il + s)
def indent(self, num=1):
'''Increase the indentation level.'''
self.il += num
def dedent(self, num=1):
'''Decrease the indentation level.'''
self.il -= num
def parse(self, elem):
'''Call the parser of the elem.tag name.
The tag name appended to "parse_" and then the name of that
function is called. If the function is not defined, then
self.parse_undefined() is called.'''
fn_name = 'parse_' + elem.tag
try:
fn = getattr(self, fn_name)
except AttributeError:
fn = self.parse_undefined
return fn(elem)
def loop(self, elem):
'''Helper method to loop through the child elements.'''
for e in elem:
self.parse(e)
def parseXMLfile(self, fname):
'''Reads the XML file and starts parsing from the root element.'''
tree = ET.parse(fname)
script = tree.getroot()
assert script.tag == 'script'
self.parse(script)
###################### ELEMENT PARSERS #######################
def parse_undefined(self, elem):
'''Called for the element that has no parser defined.'''
self.out('PARSING UNDEFINED for ' + elem.tag)
def parse_script(self, elem):
self.loop(elem)
def parse_stage(self, elem):
self.out('')
self.out('Parsing the stage: ' + elem.attrib['id'])
self.indent()
self.loop(elem)
self.dedent()
def parse_initialise(self, elem):
self.out('')
self.out('#---------- ' + elem.tag + ' ----------')
self.loop(elem)
def parse_variable(self, elem):
tt = str # default type
if elem.attrib['type'] == 'Integer':
tt = int
# elif ... etc for other types
# Conversion of the value to the type because of the later repr().
value = tt(elem.attrib['value'])
id_ = elem.attrib['id']
# Produce the line of the output.
self.out('{0} = {1}'.format(id_, repr(value)))
def parse_execute(self, elem):
self.out('')
self.out('#---------- ' + elem.tag + ' ----------')
self.loop(elem)
def parse_if(self, elem):
assert elem[0].tag == 'condition'
condition = self.parse(elem[0])
self.out('if ' + condition + ':')
self.indent()
self.loop(elem[1:])
self.dedent()
def parse_condition(self, elem):
assert len(elem) == 0
return elem.text
def parse_then(self, elem):
self.loop(elem)
def parse_else(self, elem):
self.dedent()
self.out('else:')
self.indent()
self.loop(elem)
def parse_error(self, elem):
assert len(elem) == 0
errorID = elem.attrib.get('errorID', None)
fieldID = elem.attrib.get('fieldID', None)
self.out('error({0}, {1})'.format(errorID, fieldID))
def parse_setNextStage(self, elem):
assert len(elem) == 0
self.out('setNextStage --> ' + elem.text)
if __name__ == '__main__':
parser = Parser()
parser.parseXMLfile('data.xml')
for s in parser:
print s
При использовании с данными, вставленными здесь http://pastebin.com/vRRxfWiA, скрипт выдает следующий результат:
Parsing the stage: stage1
#---------- initialise ----------
taxyear = 2012
taxyearstart = '06/04/2012'
taxyearend = '05/04/2013'
previousemergencytaxcode = '747L'
emergencytaxcode = '810L'
nextemergencytaxcode = '810L'
...
maxLimitAmount = 0
PARSING UNDEFINED for executeMethod
if $maxLimitReached$ == True:
employeepayrecord = 'N'
employeepayrecordstate = '2'
else:
employeepayrecordstate = '1'
gender = ''
genderstate = '1'
title = ''
forename = ''
forename2 = ''
surname = ''
dob = ''
dobinvalid = ''
#---------- execute ----------
if $dobstring$ != "":
validDOBCheck = 'False'
PARSING UNDEFINED for executeMethod
if $validDOBCheck$ == False:
error(224, dob)
else:
minimumDOBDate = ''
PARSING UNDEFINED for executeMethod
validDOBCheck = 'False'
PARSING UNDEFINED for executeMethod
if $validDOBCheck$ == False:
error(3007161, dob)
if $dobstring$ == "01/01/1901":
error(231, dob)
else:
error(231, dob)
Parsing the stage: stage2
#---------- initialise ----------
address1 = ''
...
Если у вас есть подходящая XML-схема для этих XML-файлов, есть такие инструменты, как GenerateDS, которые будут генерировать классы Python на их основе.
Это позволит вам загрузить все файлы в память и иметь их в качестве объектов. Как вы потом храните эти данные в другом месте... ну, вы не говорите, что хотите делать, но вы можете делать все, что обычно можете, с помощью Python.