Главная страница
Навигация по странице:

  • Запись файла XML вручную

  • Синтаксический анализ файлов XML с помощью SAX (Simple API for XML – упрощенный API для XML)

  • Программирование на Python 3. Руководство издательство СимволПлюс


    Скачать 3.74 Mb.
    НазваниеРуководство издательство СимволПлюс
    Дата10.11.2022
    Размер3.74 Mb.
    Формат файлаpdf
    Имя файлаПрограммирование на Python 3.pdf
    ТипРуководство
    #780382
    страница42 из 74
    1   ...   38   39   40   41   42   43   44   45   ...   74
    DOM (Document Object Model – объектная
    модель документа)
    Модель DOM – это стандартный API представления и манипулирова
    ния документами XML в памяти. Программный код создания и записи
    DOM в файл и анализа файла XML с применением модели DOM по сво
    ей структуре близко напоминает программный код, работающий с де
    ревом элементов, только немного длиннее.
    Мы рассмотрим метод export_xml_dom(), разделив его на две части. Ра
    бота этого метода делится на два этапа: сначала создается дерево DOM,
    отражающее данные об инцидентах, а потом оно записывается в файл.
    Как и в случае с деревом элементов, существуют программы, которые используют дерево DOM в качестве основной структуры для хранения своих данных, и в этой ситуации существующие данные просто запи
    сываются в файл, минуя первый этап.
    def export_xml_dom(self, filename):
    dom = xml.dom.minidom.getDOMImplementation()
    tree = dom.createDocument(None, "incidents", None)

    Запись и синтаксический анализ файлов XML
    369
    root = tree.documentElement for incident in self.values():
    element = tree.createElement("incident")
    for attribute, value in (
    ("report_id", incident.report_id),
    ("date", incident.date.isoformat()),
    ("aircraft_id", incident.aircraft_id),
    ("aircraft_type", incident.aircraft_type),
    ("pilot_percent_hours_on_type",
    str(incident.pilot_percent_hours_on_type)),
    ("pilot_total_hours",
    str(incident.pilot_total_hours)),
    ("midair", str(int(incident.midair)))):
    element.setAttribute(attribute, value)
    for name, text in (("airport", incident.airport),
    ("narrative", incident.narrative)):
    text_element = tree.createTextNode(text)
    name_element = tree.createElement(name)
    name_element.appendChild(text_element)
    element.appendChild(name_element)
    root.appendChild(element)
    Метод начинается с того, что получает реализацию DOM. По умолча
    нию реализация предоставляется парсером expat. Модуль xml.dom.mini
    dom предоставляет более простую и более легковесную реализацию
    DOM по сравнению с той, что предоставляется модулем xml.dom, хотя и использует объекты, которые определяются в модуле xml.dom. После получения реализации DOM можно приступать к созданию документа.
    Первый аргумент метода xml.dom.DOMImplementation.createDocument() –
    это URI пространства имен, но в нашем случае он не требуется, поэто
    му мы передаем значение None. Второй аргумент – это квалифициро
    ванное имя (имя тега корневого элемента) и третий аргумент – это тип документа, в нем мы также передаем значение None, так как у нас от
    сутствует тип документа. Создав дерево, представляющее документ,
    мы получаем корневой элемент и в цикле заполняем его информацией об инцидентах.
    Для каждого инцидента создается элемент , а для создания каждого атрибута этого элемента вызывается метод setAttribute(), ко
    торому передаются имя атрибута и значение. Так же как и в случае с деревом элементов, нам не нужно беспокоиться об экранировании символов «&», «<» и «>» (так же, как и о кавычках в значениях атри
    бутов). Для текстовых данных с названием аэропорта и комментария
    ми необходимо создать текстовые элементы, которые будут хранить сам текст, и обычные элементы (с соответствующим именем тега), ко
    торые будут играть роль родительских элементов, после чего обычные элементы (и содержащиеся в нем текстовые элементы) добавляются в текущий элемент . Как только элемент с информацией об инциденте будет заполнен, он добавляется в корневой элемент.

    370
    Глава 7. Работа с файлами fh = None try:
    fh = open(filename, "w", encoding="utf8")
    tree.writexml(fh, encoding="UTF8")
    return True
    Мы опустили блоки except и finally, так как они ничем не отличаются от тех, что мы уже видели. Этот фрагмент на
    глядно демонстрирует различия между строками с име
    нами кодировок, используемыми при работе со встроен
    ной функцией open(), и строками с именами кодировок,
    используемыми для файлов XML, о чем уже говорилось выше.
    Импортирование документа в виде дерева DOM напоминает импорти
    рование в дерево элементов, но, как и при экспортировании, для реа
    лизации импортирования требуется больший объем программного ко
    да. Мы рассмотрим функцию import_xml_dom(), разделив ее на три час
    ти, и начнем со строки с инструкцией def и определения вложенной функции get_text().
    def import_xml_dom(self, filename):
    def get_text(node_list):
    text = []
    for node in node_list:
    if node.nodeType == node.TEXT_NODE:
    text.append(node.data)
    return "".join(text).strip()
    Функция get_text() выполняет обход списка узлов (то есть дочерних узлов заданного узла) и из каждого текстового узла извлекает его текст и добавляет в конец списка текстов. В конце функция возвраща
    ет весь извлеченный текст, объединенный в одну строку, попутно уда
    лив пробельные символы в начале и в конце строки.
    try:
    dom = xml.dom.minidom.parse(filename)
    except (EnvironmentError,
    xml.parsers.expat.ExpatError) as err:
    print("{0}: import error: {1}".format(
    os.path.basename(sys.argv[0]), err))
    return False
    Преобразование содержимого файла XML в дерево DOM выполняется достаточно просто, потому что модуль всю основную работу берет на себя, но мы должны быть готовы обработать ошибки парсера expat, по
    тому что этот парсер XML, как и в случае с деревом элементов, по умолчанию используется классами DOM.
    self.clear()
    for element in dom.getElementsByTagName("incident"):
    Кодировки символов в файлах
    XML, стр. 366

    Запись и синтаксический анализ файлов XML
    371
    try:
    data = {}
    for attribute in ("report_id", "date", "aircraft_id",
    "aircraft_type",
    "pilot_percent_hours_on_type",
    "pilot_total_hours", "midair"):
    data[attribute] = element.getAttribute(attribute)
    data["date"] = datetime.datetime.strptime(
    data["date"], "%Y%m%d").date()
    data["pilot_percent_hours_on_type"] = (
    float(data["pilot_percent_hours_on_type"]))
    data["pilot_total_hours"] = int(
    data["pilot_total_hours"])
    data["midair"] = bool(int(data["midair"]))
    airport = element.getElementsByTagName("airport")[0]
    data["airport"] = get_text(airport.childNodes)
    narrative = element.getElementsByTagName(
    "narrative")[0]
    data["narrative"] = get_text(narrative.childNodes)
    incident = Incident(**data)
    self[incident.report_id] = incident except (ValueError, LookupError, IncidentError) as err:
    print("{0}: import error: {1}".format(
    os.path.basename(sys.argv[0]), err))
    return False return True
    После создания дерева DOM производится очистка сло
    варя с инцидентами и выполняются итерации по всем тегам . Из каждого тега инцидента извлекают
    ся его атрибуты, и затем даты, числа и логические значе
    ния преобразовываются в соответствующие типы дан
    ных тем же способом, который применялся при работе с деревом элементов. Единственное существенное отли
    чие между деревом DOM и деревом элементов состоит в том, как обрабатываются текстовые узлы. Сначала с помощью метода xml.dom.Element.getElementsByTagName()
    извлекаются дочерние элементы с заданными именами тегов, в данном случае это и , кото
    рые, как мы знаем, всегда присутствуют в единственном экземпляре, поэтому мы извлекаем первый (и только первый) дочерний элемент каждого из этих двух типов.
    Затем с помощью вложенной функции выполняются итерации по всем дочерним узлам этих тегов, чтобы из
    влечь текст, находящийся в них.
    Как обычно, если возникают какиелибо ошибки, соответствующие исключения перехватываются, для пользователя выводится сообще
    ние и вызывающей программе возвращается False.
    Локальные функции, стр. 409

    372
    Глава 7. Работа с файлами
    Различия между подходами с использованием DOM и дерева элемен
    тов невелики, и, поскольку в обоих случаях в конечном итоге исполь
    зуется парсер expat, оба они обладают неплохой производительностью.
    Запись файла XML вручную
    Запись в файл уже существующего дерева элементов или дерева DOM
    может быть реализована единственным вызовом метода. Но если дан
    ные еще не представлены в какойлибо из этих форм, то сначала будет необходимо создать дерево элементов или дерево DOM, хотя иногда может оказаться гораздо удобнее просто записать данные в файл, ми
    нуя этот этап.
    При создании файлов XML, чтобы получить правильно оформленный документ XML, необходимо гарантировать корректное экранирование служебных символов в тексте и в значениях атрибутов. Ниже приво
    дится программный код метода export_xml_manual(), выполняющий за
    пись данных об инцидентах в файл XML:
    def export_xml_manual(self, filename):
    fh = None try:
    fh = open(filename, "w", encoding="utf8")
    fh.write('\n')
    fh.write("\n")
    for incident in self.values():
    fh.write(' 'date="{0.date!s}" '
    'aircraft_id={aircraft_id} '
    'aircraft_type={aircraft_type} '
    'pilot_percent_hours_on_type='
    '"{0.pilot_percent_hours_on_type}" '
    'pilot_total_hours="{0.pilot_total_hours}" '
    'midair="{0.midair:d}">\n'
    '{airport}\n'
    '\n{narrative}\n\n'
    '
    \n'.format(incident,
    report_id=xml.sax.saxutils.quoteattr(
    incident.report_id),
    aircraft_id=xml.sax.saxutils.quoteattr(
    incident.aircraft_id),
    aircraft_type=xml.sax.saxutils.quoteattr(
    incident.aircraft_type),
    airport=xml.sax.saxutils.escape(incident.airport),
    narrative="\n".join(textwrap.wrap(
    xml.sax.saxutils.escape(
    incident.narrative.strip()), 70))))
    fh.write("
    \n")
    return True
    Как и прежде в этой главе, мы опустили блоки except и finally.

    Запись и синтаксический анализ файлов XML
    373
    При записи в файл используется кодировка UTF8, и ее необходимо указать в вызове встроенной функции open(). Строго говоря, кодиров
    ку можно и не указывать в объявлении потому, что кодировка
    UTF8 используется по умолчанию, но мы предпочитаем делать это яв
    но. Мы решили заключать значения атрибутов в кавычки, поэтому при добавлении данных об инциденте для обозначения строк в про
    граммном коде используются апострофы, благодаря чему отпала необ
    ходимость экранировать кавычки.
    Функция sax.saxutils.quoteattr() напоминает по своему действию функцию sax.saxutils.escape(), используемую для обработки текста
    XML, – тем, что она корректно экранирует символы «&», «<» и «>».
    Кроме того, она экранирует кавычки (если это необходимо) и возвра
    щает готовую к использованию строку, уже заключенную в кавычки.
    По этой причине нам не потребовалось окружать кавычками иденти
    фикатор отчета и другие строковые значения атрибутов.
    Символы перевода строки, которые мы вставляем, и выравнивание текста комментария – это исключительно косметическое прихораши
    вание. Сделано это только для того, чтобы содержимое файла проще было читать людям, поэтому их легко можно просто опустить.
    Запись данных в формате HTML мало чем отличается от записи дан
    ных в формате XML. Программа convertincidents.py включает в себя функцию export_html() – в качестве простого примера такой возможно
    сти, однако мы не будем рассматривать ее, потому что в ней нет ничего нового, что действительно стоило бы показать.
    Синтаксический анализ файлов XML с помощью SAX
    (Simple API for XML – упрощенный API для XML)
    В отличие от дерева элементов и DOM, которые формируют документ
    XML в памяти целиком, парсеры SAX используют принцип последо
    вательной обработки, реализация которого потенциально обладает бо
    лее высокой скоростью работы и предъявляет более низкие требова
    ния к объему памяти. Однако преимущество в скорости можно не учи
    тывать, так как реализации деревьев элементов и DOM используют быстрый парсер expat.
    Парсеры SAX, когда встречают начальные теги, конечные теги и дру
    гие элементы XML, извещают об этом посредством «событий парсин
    га». Чтобы иметь возможность обрабатывать интересующие нас собы
    тия, мы должны создать соответствующий класс обработчика и реали
    зовать в нем предопределенные методы, которые будут вызываться по соответствующим событиям. Наиболее часто в программах реализует
    ся обработчик содержимого, хотя, когда возникает необходимость в более полном управлении процессом парсинга, можно предусмотреть и реализацию обработчиков ошибок других обработчиков.

    374
    Глава 7. Работа с файлами
    Ниже приводится полный программный код метода import_xml_sax().
    Он получился очень коротким благодаря тому, что основная работа выполняется классом IncidentSaxHandler:
    def import_xml_sax(self, filename):
    fh = None try:
    handler = IncidentSaxHandler(self)
    parser = xml.sax.make_parser()
    parser.setContentHandler(handler)
    parser.parse(filename)
    return True except (EnvironmentError, ValueError, IncidentError,
    xml.sax.SAXParseException) as err:
    print("{0}: import error: {1}".format(
    os.path.basename(sys.argv[0]), err))
    return False
    Мы создали один обработчик, который будет использоваться нами, за
    тем создали экземпляр парсера SAX и передали ему в качестве обработ
    чика содержимого обработчик, созданный непосредственно перед этим. После этого мы передали методу parse() парсера имя файла и вер
    нули True, если в ходе анализа файла не возникло никаких ошибок.
    Методу инициализации обработчика класса IncidentSaxHandler был пе
    редан объект self (то есть объект класса IncidentCollection, являюще
    гося подклассом dict). Обработчик удаляет всю прежнюю информа
    цию об инцидентах и затем по мере разбора файла наполняет его новы
    ми данными об инцидентах. По завершении процесса парсинга сло
    варь будет содержать все прочитанные записи об инцидентах.
    class IncidentSaxHandler(xml.sax.handler.ContentHandler):
    def __init__(self, incidents):
    super().__init__()
    self.__data = {}
    self.__text = ""
    self.__incidents = incidents self.__incidents.clear()
    Наш собственный класс обработчика должен наследовать соответст
    вующий базовый класс. Тем самым гарантируется, что при отсутствии реализации некоторых методов (просто потому, что некоторые собы
    тия парсинга нас не интересуют) будут вызываться методы базового класса, которые не делают ничего опасного.
    В самом начале вызывается метод инициализации базового класса.
    Вообще, это желательно делать в любых подклассах, хотя для прямых наследников класса object в этом нет необходимости (но и нет никакой опасности). Словарь self.__data используется для хранения информа
    ции об инциденте, строка self.__text используется для хранения тек
    ста с названием аэропорта или комментария – в зависимости от того,

    Запись и синтаксический анализ файлов XML
    375
    какой элемент данных читается, и словарь self.__incidents является ссылкой на словарь IncidentCollection, который будет дополняться об
    работчиком напрямую. (В качестве альтернативы можно было бы соз
    дать внутри обработчика независимый словарь и копировать его в кон
    це вызовом методов dict.clear() и dict.update().)
    def startElement(self, name, attributes):
    if name == "incident":
    self.__data = {}
    for key, value in attributes.items():
    if key == "date":
    self.__data[key] = datetime.datetime.strptime(
    value, "%Y%m%d").date()
    elif key == "pilot_percent_hours_on_type":
    self.__data[key] = float(value)
    elif key == "pilot_total_hours":
    self.__data[key] = int(value)
    elif key == "midair":
    self.__data[key] = bool(int(value))
    else:
    self.__data[key] = value self.__text = ""
    Всякий раз, когда парсер встречает открывающий тег и его атрибуты,
    он вызывает метод xml.sax.handler.ContentHandler.startElement(), кото
    рому передает имя тега и его атрибуты. В файле XML, содержащем ин
    формацию об авиационных инцидентах, имеются следующие откры
    вающие теги: , который мы просто игнорируем; ,
    атрибуты которого помещаются в словарь self.__data; а также port>
    и , которые мы тоже игнорируем. Всегда, когда встре
    чается открывающий тег, мы очищаем строку self.__text, потому что в формате файла XML с информацией об авиационных инцидентах от
    сутствуют вложенные текстовые теги.
    Мы не предусматриваем обработку исключений в классе IncidentSax
    Handler
    . В случае появления исключения оно будет передано вызываю
    щему методу, в данном случае – методу import_xml_sax(), который пе
    рехватит его и выведет соответствующее сообщение об ошибке.
    def endElement(self, name):
    if name == "incident":
    if len(self.__data) != 9:
    raise IncidentError("missing data")
    incident = Incident(**self.__data)
    self.__incidents[incident.report_id] = incident elif name in frozenset({"airport", "narrative"}):
    self.__data[name] = self.__text.strip()
    self.__text = ""
    Когда парсер встречает закрывающий тег, он вызывает метод xml.sax.
    handler.ContentHandler.EndElement()
    . Если был достигнут конец записи

    376
    Глава 7. Работа с файлами об инциденте, все необходимые данные уже должны быть собраны, по
    этому остается только создать новый объект Incident и добавить его в словарь с инцидентами. Если был обнаружен закрывающий тег тек
    стового элемента, в словарь self.__data добавляется новый элемент с текстом, извлеченным к данному моменту. В конце метод очищает строку self.__text, подготавливая ее к дальнейшему использованию.
    (Строго говоря, ее можно и не очищать, так как она очищается при об
    наружении открывающего тега, но очистка может потребоваться при работе с некоторыми другими форматами XML, например, где имеют
    ся вложенные теги.)
    def characters(self, text):
    self.__text += text
    Когда парсер SAX встречает текст, он вызывает метод xml.sax.hand
    ler.ContentHandler.characters()
    . Нет никакой гарантии, что этот метод будет вызван один раз для всего текста – текст может передаваться час
    тями. По этой причине метод просто накапливает текст, а запись текста в словарь выполняется, только когда будет встречен соответствующий закрывающий тег. (Более эффективно было бы сделать переменную self.__text списком, тело этого метода – вызовом метода self.__text.ap
    pend(text)
    и внести соответствующие изменения в другие методы.)
    Реализация с использованием SAX API существенно отличается от реализации с использованием дерева элементов или DOM, но она на
    много эффективнее. Мы можем реализовать другие обработчики и пе
    реопределить другие методы в обработчике содержимого, чтобы полу
    чить более полный контроль над процессом парсинга. Парсер SAX не поддерживает возможность создания представления документа XML,
    что делает его идеальным инструментом для чтения данных в формате
    XML в наши собственные коллекции, но это также означает, что при использовании SAX в памяти нет никакого «документа», готового к записи в файл в формате XML, поэтому запись должна выполняться с использованием одного из подходов, рассматривавшихся выше в этом разделе.
    1   ...   38   39   40   41   42   43   44   45   ...   74


    написать администратору сайта