Есть ли способ написать менее повторяющийся код? Запись файла XML в python с использованием ElementTree - PullRequest
0 голосов
/ 18 января 2020

В настоящее время я учусь кодировать в python, но работа с файлами XML доставляет мне некоторые проблемы. Я пытался написать XML -файл, используя некоторые данные, которые я отфильтровал из JSON -файла.

Файл XML, который я хочу записать, должен выглядеть следующим образом:

<?xml version='1.0' encoding='UTF-8'?>
<collection>
    <work>
        <title>Title</title>
        <dimensions>
            <width>Width (cm)</width>
            <height>Height (cm)</height>
        </dimensions>
        <acquisition>
            <number>AccessionNumber</number>
            <year>year of DateAcquired</year>
        </acquisition>
    </work>
  [...]
</collection>

Он может быть записан в одну строку в XML, так как не нужно быть красивым.

Мой python код на данный момент выглядит следующим образом:

import xml.etree.ElementTree as ET
root = ET.Element('collection')
tree = ET.ElementTree(root)

for artwork in artworks_filtered_list:
    work = ET.SubElement(root, 'work')
    title = ET.SubElement(work, 'title')
    title.text = artwork['Title']
    dimensions = ET.SubElement(work, 'dimensions')
    if 'Width (cm)' in artwork:
        width = ET.SubElement(dimensions, 'width')
        width.text = str(artwork['Width (cm)'])
    height = ET.SubElement(dimensions, 'height')
    height.text = str(artwork['Height (cm)'])
    acquisition = ET.SubElement(work, 'acquisition')
    number = ET.SubElement(acquisition, 'number')
    number.text = str(artwork['AccessionNumber'])
    year = ET.SubElement(acquisition, 'year')
    year.text = str(artwork['DateAcquired'][:4])

tree.write('example.xml', encoding='UTF-8', xml_declaration=True)

Поскольку ширина отсутствует в некоторых графических данных, мне нужно было проверить, существует ли она для каждой записи , В противном случае я получаю сообщение об ошибке. artworks_filtered_list - это список словарей, который содержит записи для различных произведений искусства и выглядит следующим образом:

artworks_filtered_list = [
    {
        "Title": "Interval",
        "Artist": ["David Hartt"],
        "ConstituentID": [47183],
        "ArtistBio": ["Canadian, born 1967"],
        "Nationality": ["Canadian"],
        "BeginDate": [1967],
        "EndDate": [0],
        "Gender": ["Male"],
        "Date": "2016",
        "Medium": "Aluminum and tempered glass",
        "Dimensions": 'Wall: 102 × 218 × 4" (259.1 × 553.7 × 10.2 cm)',
        "CreditLine": "Fund for the Twenty First Century",
        "AccessionNumber": "1772.2015.5",
        "Classification": "Installation",
        "Department": "Media and Performance Art",
        "DateAcquired": "2015-12-11",
        "Cataloged": "Y",
        "ObjectID": 205745,
        "URL": "http://www.moma.org/collection/works/205745",
        "ThumbnailURL": None,
        "Depth (cm)": 10.16002032,
        "Height (cm)": 259.080518161,
        "Width (cm)": 553.7211074422,
    },
    ...,
]

Это мой код прямо сейчас. Он работает и создает XML -файл по назначению, но я чувствую, что кода может быть больше, чем нужно. Есть ли способ получить тот же результат с менее повторяющимся / более красивым кодом? (Все равно следует использовать ElementTree)

1 Ответ

0 голосов
/ 18 января 2020

Я бы, вероятно, использовал какое-то отображение для уменьшения количества избыточного кода. Примерно так:

#!/usr/bin/python

import datetime

import xml.etree.ElementTree as ET


artworks_filtered_list = [...as in your example...]


# This by itself reduces the amount of code by about 50% :)
def add_text_element(root, tag, text):
    new = ET.SubElement(root, tag)
    new.text = text


# According to your question, acquisition.year should be just the
# year from DateAcquired, so we need a method to extract and return
# the year.
def extract_year(val):
    dt = datetime.datetime.strptime(val, '%Y-%m-%d')
    return dt.year


# This is called once for every work of art in artworks_filtered_list
def append_work(root, work):

    # create the <work> container element
    new = ET.SubElement(root, 'work')

    # create empty dimension and acquisition elements. This adds them
    # to the new <work> element.
    dimensions = ET.SubElement(new, 'dimensions')
    acquisition = ET.SubElement(new, 'acquisition')

    # Add our title
    add_text_element(new, 'title', work['Title'])

    # Now build a map that will link keys from a work of art in
    # artworks_filtered_list to XML elements. Each item in this list is
    # a 4-tuple, where the items are:
    #
    # 1. The parent element to which we will be adding a new element
    # 2. The name of the new element
    # 3. The dictionary key from which we will get the text value
    # 4. A function to transform the value, if necessary (or None)
    #
    attrmap = [
        (dimensions, 'width', 'Width (cm)', None),
        (dimensions, 'height', 'Height (cm)', None),
        (acquisition, 'number', 'AccessionNumber', None),
        (acquisition, 'year', 'DateAcquired', extract_year),
    ]

    # And now use the above map to transform the artwork dictionary
    # into XML elements.
    for parent, tag, key, xform in attrmap:
        if key in work:
            add_text_element(parent, tag,
                             str(xform(work[key]) if xform else work[key]))


def main():
    root = ET.Element('collection')

    for work in artworks_filtered_list:
        append_work(root, work)

    print(ET.tostring(root).decode('utf-8'))


if __name__ == '__main__':
    main()

Учитывая ваш пример ввода, вышеприведенный код выдает:

<collection>
  <work>
    <dimensions>
      <width>553.7211074422</width>
      <height>259.080518161</height>
    </dimensions>
    <acquisition>
      <number>1772.2015.5</number>
      <year>2015</year>
    </acquisition>
    <title>Interval</title>
  </work>
</collection>

... хотя на самом деле он не выглядит красиво. Если бы вы использовали lxml.etree вместо xml.etree, tostring смог бы печатать для вас XML.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...