Обратите внимание: Этот пост был первоначально опубликован на Dzone. Я изменил заголовок, поскольку я уже писал несколько XML парсеров и не хочу запутать моих читателей.
Одна из задач, с которыми я часто сталкиваюсь в своей работе - это взять данные в одном формате, обработать их и создать отчёт или какой-то другой документ. Сегодня мы посмотрим на то, как взять XML ввод, распарсить его при помощи Python и затем создать отчёт при помощи Reportlab - стороннем пакете для Python. Давайте предположим, что моя компания получила заказ на три предмета, который я должен выполнить. Такой XML может выглядеть так:
<?xml version="1.0"?> <invoice> <order_number>456789</order_number> <customer_id>789654</customer_id> <address1>John Doe</address1> <address2>123 Dickens Road</address2> <address3>Johnston, IA 55555</address3> <address4/> <order_items> <item> <id>11123</id> <name>Expo Dry Erase Pen</name> <price>1.99</price> <quantity>5</quantity> </item> <item> <id>22245</id> <name>Cisco IP Phone 7942</name> <price>300</price> <quantity>1</quantity> </item> <item> <id>33378</id> <name>Waste Basket</name> <price>9.99</price> <quantity>1</quantity> </item> </order_items> </invoice>
Сохраним его как order.xml. Теперь мне надо написать парсер и генератор PDF. Вы можете использовать встроенные библиотеки для парсинга XML: SAX, minidom или ElementTree; или же Вы можете загрузить один из сторонних пакетов для этого. Моим любимым является lxml, который содержит версию ElementTree и очень хороший код под названием “objectify”. Этот код берёт XML и превращает его объект Python c точечной нотацией. Я использую его для парсинга так как он позволяет сделать это легко, просто и понятно. Для создания PDF я буду использовать Reportlab.
Вот простой скрипт, который делает всё, что нам надо:
from decimal import Decimal from lxml import etree, objectify from reportlab.lib import colors from reportlab.lib.pagesizes import letter from reportlab.lib.styles import getSampleStyleSheet from reportlab.lib.units import inch, mm from reportlab.pdfgen import canvas from reportlab.platypus import Paragraph, Table, TableStyle ######################################################################## class PDFOrder(object): """""" #---------------------------------------------------------------------- def __init__(self, xml_file, pdf_file): """Constructor""" self.xml_file = xml_file self.pdf_file = pdf_file self.xml_obj = self.getXMLObject() #---------------------------------------------------------------------- def coord(self, x, y, unit=1): """ # http://stackoverflow.com/questions/4726011/wrap-text-in-a-table-reportlab Вспомогательный класс для позиционирования в объектах Canvas """ x, y = x * unit, self.height - y * unit return x, y #---------------------------------------------------------------------- def createPDF(self): """ Создаём PDF на основании XML данных """ self.canvas = canvas.Canvas(self.pdf_file, pagesize=letter) width, self.height = letter styles = getSampleStyleSheet() xml = self.xml_obj address = """ <font size="9"> SHIP TO:<br/> <br/> %s<br/> %s<br/> %s<br/> %s<br/> </font> """ % (xml.address1, xml.address2, xml.address3, xml.address4) p = Paragraph(address, styles["Normal"]) p.wrapOn(self.canvas, width, self.height) p.drawOn(self.canvas, *self.coord(18, 40, mm)) order_number = '<font size="14"><b>Order #%s </b></font>' % xml.order_number p = Paragraph(order_number, styles["Normal"]) p.wrapOn(self.canvas, width, self.height) p.drawOn(self.canvas, *self.coord(18, 50, mm)) data = [] data.append(["Item ID", "Name", "Price", "Quantity", "Total"]) grand_total = 0 for item in xml.order_items.iterchildren(): row = [] row.append(item.id) row.append(item.name) row.append(item.price) row.append(item.quantity) total = Decimal(str(item.price)) * Decimal(str(item.quantity)) row.append(str(total)) grand_total += total data.append(row) data.append(["", "", "", "Grand Total:", grand_total]) t = Table(data, 1.5 * inch) t.setStyle(TableStyle([ ('INNERGRID', (0,0), (-1,-1), 0.25, colors.black), ('BOX', (0,0), (-1,-1), 0.25, colors.black) ])) t.wrapOn(self.canvas, width, self.height) t.drawOn(self.canvas, *self.coord(18, 85, mm)) txt = "Thank you for your business!" p = Paragraph(txt, styles["Normal"]) p.wrapOn(self.canvas, width, self.height) p.drawOn(self.canvas, *self.coord(18, 95, mm)) #---------------------------------------------------------------------- def getXMLObject(self): """ Открываем XML документ и возвращаем lxml XML документ """ with open(self.xml_file) as f: xml = f.read() return objectify.fromstring(xml) #---------------------------------------------------------------------- def savePDF(self): """ Сохраняем PDF """ self.canvas.save() #---------------------------------------------------------------------- if __name__ == "__main__": xml = "order.xml" pdf = "letter.pdf" doc = PDFOrder(xml, pdf) doc.createPDF() doc.savePDF()
Вот итоговый PDF: letter.pdf
Давайте потратим немного времени на разбор этого кода. Во-первых, мы импортируем набор разных объектов для настройки нашего окружения и получения нужных компонентов из Reportlab и lxml. Кроме того, я импортирую модуль decimal, так как для финансовых расчётов я хочу более точный инструмент, чем стандартная математическая библиотека Python. Затем я создаю класс PDFOrder, который принимает два аргумента - пути к xml файлу и pdf файлу. При его инициализации мы создаём несколько свойств класса, читаем XML файл и возвращаем XML объект. Метод coord используется для позиционирования flowables в Reportlab, которые являются динамическими объектами, могут разделяться между страницами и принимать различные стили.
Метод createPDF - сердце нашей программы. Объект canvas используется для создания нашего PDF и "рисования" на нём. Я настроил его на размер letter и получил лист стилей по умолчанию. Затем я создал адрес доставки и разместил его вверху страницы, 18mm слева и 40mm сверху. После этого я создал и разместил номер заказа. Наконец, при помощи итератора, я прошёлся по элементам заказа и разместил их во вложенном списке, который поместил в Reportlab’s Table flowable. И, в завершении, я разместил саму таблицу и задал её стиль. После этого сохранил файл на диск и всё.
Документ создан и у меня есть хороший образец для коллег. На данный момент мне осталось лишь навести лоск на документ, используя различные стили текста (например, bold, italic, размер шрифта) или изменяя немного расположение элементов. Это, обычно, зависит уже от клиента или менеджера, так что буду ждать их отзыва.
Теперь Вы знаете как распарсить XML документ в Python и создать на основе полученных данных PDF.
Комментариев нет:
Отправить комментарий