Время прочтения: 2 мин.

В сети существует множество руководств, где авторы бодро разбирают структуру XML известного документа – решительно находят root у дерева, после чего мужественно пробегают циклом по тэгам, при этом циклы используют столько раз, сколько уровней вложенности у документа. Это все хорошо и отлично, когда мы знаем сколько раз нужно натравить цикл на очередной уровень вложенности.

Но у нас возникла задача, с которой мы еще не сталкивались, когда есть много документов XML, а документации по структуре данных нет, вообще. Нет, не так…. НЕТ ВООБЩЕ!. Как в таком случае, мы с помощью имеющегося опыта можем опознать структуру и глубину текущих файлов?

Данные XML хранятся в базе в текстовом виде — xml_text. Для начала считаем и опознаем то, что можно – получим root документа – он есть у всех.

from lxml import etree as ET
from io import BytesIO
import os

byte_obj = BytesIO(xml_text.encode())
tree = ET.parse(byte_obj)
root = tree.getroot()

Так как у нас нет никакой гарантии, что файл, который будет записан завтра, не будет иметь другую структуру, для начала, прокатимся на велосипеде рекурсивного алгоритма, и получим структуру в линейном виде. Так как нам важен порядок, то сохранять данные будем в список с именем xml_data.

xml_data = []
deffind_Etree(xml_elements, level=0):
# Формируем список вида - уровень, тэг, тектовое значение
ifstr(type(xml_elements)) == "<class 'lxml.etree._Element'>":
level = level
forxml_elementinxml_elements:
xml_data.append((level, xml_element.tag.split('}')[1], xml_element.text))
find_Etree(xml_element, level+1)   
returnxml_elements

Результат работы сохранен в список xml_data и имеет следующую структуру — список кортежей, где каждый кортеж — это уровень вложения от корневого тэга, наименование тэга и значение тэга.

[(0, 'ProductType', 'новый продукт'),
 (0, 'AgrNum', '1-YYYYYY'),
(0, 'AgrDate', '22.07.2014'),
(0, 'Account', None),
(1, 'INN', '9900000099'),
(1, 'Name', 'Компания'),
 (0, 'User', None),
 (1, 'FIO', 'Мошкин Сергей Михайлович'),
(1, 'Position', 'ANALYST'),
 (0, 'ListOfCollateral', None),
 (1, 'Collateral', None),
 (2, 'CRMId', 'Квартиры'),
 (2, 'CollType', 'OSNResidential_Real_Estate'),
 (2, 'AssessType', 'Market'),
(2, 'AssessSource', 'Int_Appraiser')]

Полученный список уже можно разносить по таблицам для формирования реляционной БД. Каждый уровень вложения — это новая таблица (наименование столбца — наименование тэга xml, значения в столбце – значения тэга xml). Так, уровень вложения 1 — это новая таблица, она связана с таблицей из данных уровня 0 через ключевое поле (внешний ключ). Каждое последующее вложение — это новая таблица, логика связи с таблицей предыдущего уровня такая же.

Реализация данного проекта позволила еще раз на практике доказать, что многое уже придумали за нас и, прежде чем изобретать что-то новое, нужно максимально использовать прошлый опыт и ранее написанный код.