Когда мы представляем минимальный парсер , при чтении файла XML нужно реагировать только на пару событий:
- Открытие
<STUDENT>
встречается: Подготовьте новый список, чтобы мы могли хранить различные значения, которые мы увидим. - Открытие
<NAME>
, <REGNUM>
et c. встречается: Узнайте, в какой слот это значение свойства должно быть go («текущий» слот). - Обнаружен фрагмент текста: если этот текст принадлежит интересующему нас свойству, напишите это к текущему слоту.
- Закрытие
</NAME>
, </REGNUM>
et c. встречается: Обратите внимание, что любой следующий текст не интересен и должен игнорироваться. - Встречается закрывающий
</STUDENT>
: мы закончили с текущим студентом и можем вывести все данные, которые мы собрали.
Имея это в виду, мы можем создать обработчик событий SAX, который реализует эту идею:
from xml.sax.handler import ContentHandler
class StudentReader(ContentHandler):
def __init__(self, callback=None):
self.column_order = 'ID,NAME,REGNUM,COUNTRY,SHORT_STD_DESC'
self.current_student = None
self.current_idx = None
self.mapping = {key: idx for idx, key in enumerate(self.column_order.split(','))}
self.num_cols = len(self.mapping)
self.callback = callback
def startElement(self, tag, attrs):
if tag == 'STUDENT':
# new student with correct number of columns
self.current_student = [''] * self.num_cols
elif tag in self.mapping:
# which column are we writing to?
self.current_idx = self.mapping[tag]
else:
self.current_idx = None
def endElement(self, tag):
if tag == 'STUDENT':
if self.callback is not None:
# when we have a callback, call it
self.callback(self.current_student)
else:
# without a callback, just print to console (for debugging)
print(self.current_student)
elif tag in self.mapping:
self.current_idx = None
def characters(self, data):
if self.current_idx is not None:
self.current_student[self.current_idx] += data
Больше ничего не нужно сохранять, дерево не нужно строить, мы можем сделайте это , пока читаете ввод XML. Это будет быстро и будет использовать очень мало памяти.
Теперь мы можем создать синтаксический анализатор SAX и дать ему StudentReader
экземпляр:
import xml.sax
handler = StudentReader()
xml.sax.parse('data.xml', handler)
... который выводит списки на консоль:
['JH1', 'JOHN', '1000', 'USA', 'JOHN IS A GOOD STUDENT']
['AD2', 'ADAM', '1001', 'FRANCE', 'ADAM IS A GOOD STUDENT']
['PE5', 'PETER', '1003', 'BELGIUM', 'PETER IS A GOOD STUDENT']
['ER7', 'ERIC', '1006', 'AUSTRALIA', 'ERIC IS A GOOD STUDENT']
['NI8', 'NICHOLAS', '1009', 'GREECE', 'NICHOLAS IS A GOOD STUDENT']
Но, возможно, более интересно записать данные, например, в файл CSV.
Для этого и нужен аргумент callback
в нашем StudentReader
- мы могли бы передать эти списки непосредственно в модуль записи CSV, чтобы он мог записать выходной файл , в то время как входной файл в процессе обработки.
import csv
import xml.sax
with open('output.csv', 'w', encoding='utf8', newline='') as fp:
writer = csv.writer(fp, delimiter=';')
handler = StudentReader(writer.writerow)
xml.sax.parse('data.xml', handler)
Конечно, вы также можете использовать команду базы данных в обратном вызове или что угодно.