Python: сравнение и подсчет структур словарей в тысячах словарей / XML / JSON

Я разбираю тысячи XML-файлов в словари и сохраняю их структуры в JSON.

У них почти такая же структура, но количество различных схем именования тегов неизвестно. Существует множество различных сокращений для именования тегов в этих тысячах файлов.

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

Для этого я хочу создать один главный словарь XML / словарей, который включает все варианты имен тегов и, желательно, их количество в тысячах XML / словарей.

Вот небольшой образец одного из словарей:

{
    "Header": {
        "Ts": {},
        "PeriodEndDt": {},
        "PreparedBy": {
            "PreparerID": {},
            "PreparerFirmName": {
                "BusinessNameLine1Txt": {}
            },
            "PreparerAddress": {
                "AddLn1Txt": {},
                "CityName": {},
                "StateAbbreviationCd": {},
                "ZIPCd": {}
            }
        },
        "FormTypeCd": {},
        "PeriodBeginDt": {},
        "Filer": {
            "UniqueID": {},
            "BusinessName": {
                "BusinessNameLine1Txt": {}
            },
            "BusinessNameControlTxt": {},
            "PhoneNum": {},
            "USAddress": {
                "AddressLine1Txt": {},
                "CityNm": {},
                "StateAbbreviationCd": {},
                "ZIPCd": {}
            }
        },

        "FormData": {
            "FormCodeType": {
                "BizType": {},
                "AssetsAtEOY": {},
                "AccountingMethod": {},
                "RevenueAndExpenses": {
                    "ScheduleBNotReqd": {},
                    "DivsRevAndExpenses": {},
                    "DivsNetInvstIncomeAmt": {},
                    "NetGainSaleAstRevAndExpnssAmt": {},
                    "RevsOvrExpenses": {},
                    "NetInvestmentIncomeAmt": {}
                },
                "BalanceSheetGroup": {
                    "CashInvstBOYAmt": {},
                    "CashInvstEOYAmt": {},
                    "CashInvstEOYFMVAmt": {},
                    "OtherInvestmentsBOYAmt": {},
                    "OtherInvestmentsEOYAmt": {},
                    "CapitalStockEOYAmt": {},
                    "TotalLiabilitiesNetAstEOYAmt": {}
                },
                "ChangeNetAssetsFundGroup": {
                    "NetAssettFundBalancesBOYAmt": {},
                    "ExcessRevExpensesAmt": {},
                    "OtherIncreasesAmt": {},
                    "SubtotalAmt": {},
                    "OtherDecreasesAmt": {},
                    "TotNetAstOrFundBalancesEOYAmt": {}
                },
                "CapGainsLossTxInvstIncmDetail": {
                    "CapGainsLossTxInvstIncmGrp": {
                        "PropertyDesc": {},
                        "HowAcquiredCd": {},
                        "GrossSalesPriceAmt": {},
                        "GainOrLossAmt": {},
                        "GainsMinusExcessOrLossesAmt": {}
                    },
                    "StatementsRegardingActyGrp": {
                        "LegislativePoliticalActyInd": {},
                        "MoreThan100SpentInd": {}
                    },
                    "PhoneNum": {},
                    "LocationOfBooksUSAddress": {
                        "AddressLine1Txt": {},
                        "CityNm": {},
                        "StateAbbreviationCd": {},
                        "ZIPCd": {}
                    },
                    "CorporateDirectorsGrp": {
                        "DirectorsGrp": {
                            "PersonNm": {},
                            "USAddress": {
                                "AddressLine1Txt": {},
                                "CityNm": {},
                                "StateAbbreviationCd": {},
                                "ZIPCd": {}
                            },
                            "EmpPrograms": {
                                "EmployeeBenefitGroupNum": {},
                                "GroupType": {
                                    "GroupElement": {},
                                    "GroupCharacter": {
                                        "GroupNames": {}
                                    }
                                }

                            },
                            "EmpOffice1": {},
                            "EmpOffice2": {},
                            "EmpOffice3": {},
                            "EmpOffice4": {}
                        }


                    }
                }
            }
        }
    }
}

Код, который я использую для создания словарей / JSON в первую очередь, выглядит следующим образом:

import xml.etree.ElementTree as ET

strip_ns = lambda xx: str(xx).split('}', 1)[1]
tree = ET.parse('xmlpath.xml')
root = tree.getroot()


tierdict = {}
for tier1 in root:
    tier1var = strip_ns(tier1.tag)
    tierdict[tier1var] = {}
    for tier2 in tier1:
        tier2var = strip_ns(tier2.tag)
        tierdict[tier1var][tier2var] = {}
        for tier3 in tier2:
            tier3var = strip_ns(tier3.tag)
            tierdict[tier1var][tier2var][tier3var] = {}
            for tier4 in tier3:
                tier4var = strip_ns(tier4.tag)
                tierdict[tier1var][tier2var][tier3var][tier4var] = {}

Результат, который я хотел бы увидеть, выглядит примерно так:

{
    "Header": {
        "Header.Count": 5672,
        "Ts": {
            "Ts.Count": 3365
            },
        "Ss": {
            "Ss.Count": 2328
            },
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
0
75
1

Ответы 1

Я бы, вероятно, выполнил рекурсивный поиск нужных вам элементов, как определено ниже:

def get_elements(json_entry, child_elements=[]):

     if not child_elements:
         return json_entry

     el, other_children = child_elements[0], child_elements[1:]

     children = el.getchildren()
     rec = json_entry.get(el.tag)
     if not children:
         json_entry[el.tag] = {"Count": rec.get("Count",0)+1 if rec else 1}

     else:
         json_entry[el.tag] = {"Count": rec.get("Count",0) if rec else 1,
                                    **get_elements({}, children)}

     return get_elements(json_entry, other_children)

Таким образом, вы можете просто передать корневой элемент вашего xml:

from lxml import etree

with open("myxml.xml", "r") as fh:
    tree = etree.parse(fh)

root = tree.getroot()

root_children = root.getchildren()

child_recs = get_elements({}, root_children)

{'tagOne': {'Count': 1}, 'tagTwo': {'Count': 1, 'tagThree': {'Count': 1}, 'tagFour': {'Count': 1, 'tagFive': {'Count': 1}}}}

Если вы хотите обернуть вокруг него корневой элемент, сделайте это так:

master_lookup = {root.tag: {"Count": 1, **child_recs}}

Это может быть легко расширено до цикла for через множество файлов.

master_lookup = {}

for file in os.walk(path):
    with open(file) as fh:
        tree = etree.parse(fh)

    root = tree.getroot()
    root_entry = master_lookup.get(root.tag, {"Count": 0})
    root_children = root.getchildren()

    root_count = root_entry.pop("Count")

    master_lookup[root.tag] = {"Count": root_count, **get_elements({**root_entry}, root_children)}

Что-то в этом роде

Спасибо за подробный ответ! Откуда вы берете getchildren()? Везде, где я это видел, это не часть Python 3.

misterflister 18.09.2018 21:38
getchildren является функцией Element (часть etree). Таким образом, каждый Element в дереве имеет этот метод. Он вернет список всех дочерних элементов (пустой список, если его нет), поэтому распаковка не выдает IndexError
C.Nivs 18.09.2018 21:41

Я запускаю это в Python 3.6, если это важно

C.Nivs 18.09.2018 21:41

Еще раз спасибо за вашу помощь. Я все еще пытаюсь заставить это работать. На данный момент я, должно быть, делаю что-то не так с циклом, так что окончательное значение master_lookup, кажется, получено только из одного файла со всеми counts = 1 и кажущимся количеством отдельных полей из различных файлов, не включенных в этот диктат.

misterflister 18.09.2018 23:47

Так что для цикла файлов это не на 100% правильно. Я сделаю правку

C.Nivs 19.09.2018 17:54

Большое вам спасибо за все это! Я все еще не совсем там. Некоторые области, над которыми я работаю: при первом прохождении цикла, после первого определения root_entry, его значение равно {'Count': 0}. При последующих запусках он, кажется, принимает в качестве значения весь набор тегов из XML-файла, но затем начинает с того же самого {'Count': 0} в качестве своей первой пары kv. С другой стороны, значение root_children в основном цикле кажется именно тем, что я ожидал каждый раз: пара тегов, вложенных непосредственно под корень. И в целом вывод master_lookup такой же, как и раньше.

misterflister 20.09.2018 04:25

Проделав немного больше работы над этим, get_elements, похоже, делает работу по созданию словаря для любого 1 файла с правильным вложением и счетчиком 1. Теперь вопрос заключается в том, как сделать внешний цикл, вызываемый master_lookup, счетчиком с сравнением / кроме того, поскольку он просто заполняет dict последним запущенным файлом.

misterflister 20.09.2018 06:46

Извините за всю головную боль, рекурсия - это определенно то, что вам нужно. Цикл должен захватывать root.tag из текущего dict. Если он есть, он pops подсчитывает, таким образом, он может просто выгружать остальное в функцию рекурсии. Похоже, что для второго rec.get("Count", 0) нужен +1, в противном случае, как вы заявили, нет никакого дополнения.

C.Nivs 20.09.2018 15:18

Другие вопросы по теме