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

Xes – принятый повсеместно для Process Mining формат логов, написанный на языке XML.

XML – язык разметки документов в форматах xml, xes, rss, xhtml, fb2, широко используемый во всех областях IT. Этот формат не табличный и требует предварительной обработки перед комфортным просмотром.

Пример такой разметки xes файла, открытого в блокноте:

Любой документ в формате XML начинается с корневого элемента – это обязательная часть документа. Он может включать вложенные в него элементы и символьные данные.

Вложенные элементы, в свою очередь, могут включать вложенные элементы и символьные данные и так далее. Символьные данные бывают трех типов – тэги (названия элементов), атрибуты (характеристики элементов в виде пар ключ-значение) и привязанный к элементам текст. Получается, что схема данных хранится вместе с самими данными в виде структуры связей между элементами, и может быть неочевидна на первый взгляд.

Часто к XML файлам прилагаются описания схемы данных, помогающие при извлечении информации – они могут быть на сайте, с которого была скачана xml, или в текстовом поле корневого элемента, но бывает так что этой схемы нет, или она применима не ко всем элементам, или описывает не все типы символьных данных. Схема данных xes статична и расположена по ссылке

Если схема данных известна, данные нетрудно извлечь неавтоматическим способом, как было описано ранее в статье NTA

Но бывает так что схема данных неизвестна, поэтому решение нужно универсальное.

Когда мы говорим о парсинге, мы подразумеваем запись символьных данных из документа в формате xml в таблицу. Иерархические связи между элементами, порой, гораздо сложнее, чем таблица, поэтому для автоматического извлечения данных придется эти связи тем или иным образом учитывать и раскладывать в строки таблицы, записывая отдельные символьные данные в разные колонки (далее – фичи).

Проблема же неизвестной глубины вложенности легко решается при помощи рекурсии. Этот процесс мы тоже освещали в другой стате NTA

Мы будем проверять каждый элемент на наличие вложенных элементов, и если они будут, то мы будем проверять их на наличие вложенных элементов и так далее. Важным плюсом табличного подхода будет возможность исправить все ошибки чтения, кодировки, форматов времени и прочего, что может помешать нам использовать xes файл в чувствительных к этому программных продуктах. В качестве примера публично доступных логов используем информацию с соревнований BPIC 2020

Для автоматической обработки мы просто предположим, что каждому элементу, вложенному в корневой элемент, соответствует одна строка таблицы. Возникнет проблема повторяющихся фич, ведь более глубоко вложенные элементы повторяются. Решить эту проблему можно несколькими путями – просто игнорировать повторения, склеивать их в одну строку, дописывать их в list внутри ячейки таблицы или даже создавать новые строки или столбцы. Наиболее универсальным решением является запись в list, поскольку структура результирующей таблицы (в данном случае, pandas dataframe) не будет нарушенной, при этом данные сохранят гибкость обработки.

import xml.etree.cElementTree as et
import pandas as pd

name="PrepaidTravelCost.xes_"

tree=et.parse(name)
root=tree.getroot()
depth = 0
element_list = []
for i in range(1,len(root)):
    tempdict = {}
    tempdict = recursive_xml_parse(root[i],tempdict,depth)
    element_list.append(tempdict)
xml_df = pd.DataFrame(element_list,dtype = "str") 
xml_df.T

При помощи встроенной библиотеки xml.etree получим список элементов, входящих в корневой элемент root. Одной ячейке результирующей таблицы будет соответствовать один из этих элементов. Для превращения их содержимого в строку к каждому из них применится рекурсивная функция recursive_xml_parse:

def recursive_xml_parse(xml_node,global_dict,depth):
    depth = depth+1
    tempdict_inner = xml_node.attrib #extract information from element
    tempdict_inner["tag"+str(depth)] = xml_node.tag #also extract tag and text.
    if (xml_node.text!=None):
        tempdict_inner["text"+str(depth)] = xml_node.text     
    #check if global dictionary has keys that we just extracted
    for key,value in tempdict_inner.items():
        if key in global_dict.keys():
            try: #try to append value if key exists
                global_dict[key].append(value)
            except: #create list if not
                global_dict[key] = [global_dict[key],value]
        else: #add key if it wasnt in global dictionary
            global_dict[key] = value 
    for xml_inner_node in xml_node: #call recursively
        global_dict = recursive_xml_parse(xml_inner_node,tempdict,depth) 
            

    return global_dict

Она работает в рамках одного элемента xes файла, извлекая его атрибуты, тэг и текст, дописывая их в словарь, каждый ключ которого будет являться колонкой в таблице. Если ключ повторяется, функция присоединяет новые значения в список к старым. Поскольку у тэга нет содержимого, само его название записывается в колонку tag с номером глубины вложенности.

Результат обработки приведен ниже. Для наглядности колонки транспонированы:

Видно, что важные для Process Mining данные находятся в атрибутах key  и value, при этом все прочие атрибуты нам не пригодится. Для получения пригодного для process mining датасета достаточно лишь присвоить каждому значению списков из колонки key соответствующие значения списков из колонки value:

templist = []
xml_df_cut = xml_df[3:]
for x in range(3,len(xml_df_cut)):
    tempdict = {}
    for y in range(len(xml_df_cut["key"])):
        try:
            tempdict[xml_df_cut["key"][x][y]] = xml_df_cut["value"][x][y]
        except:
            pass
    templist.append(tempdict)
df = pd.DataFrame(templist)
df.head().T

Полученная таблица гораздо нагляднее исходного файла, при этом ее можно с тем же успехом использовать в Process Mining, как и xes файл, а также сохранить в excel. На этом же этапе можно исправить понимаемый не всеми программами формат даты:

df["fixed_timestamp"] = pd.to_datetime(df["time:timestamp"],utc=True).dt.date.astype("str")

Этот код так же сработает и для любого другого xml файла – вот пример парсинга единого реестра малого и среднего бизнеса, размещенного по ссылке

Скачаем один файл и прогоним его через тот же код, изменив лишь

name = "VO_RRMSPSV_0000_9965_20200513_0a81f9cc-dc17-4f8e-b30b-f524338ed8bd.xml"

Видно, что решение для xes файлов оказалось настолько универсальным, что сработало и для другого формата документа. Таким образом можно получить табличное представление любого файла на языке XML – xml, xes, rss, xhtml, fb2. – примеры в jupyter notebook по ссылке ниже.

Ссылка на код