Как эффективно обнаружить XML-схему, не имея всего файла в Python - PullRequest
0 голосов
/ 03 декабря 2018

У меня очень большой файл канала, который отправляется в виде документа XML (5 ГБ).Какой самый быстрый способ разобрать структуру основного узла элемента, не зная ранее его структуру?Есть ли в Python средства сделать это «на лету», не загружая полный xml в память?Например, что если я просто сохраню первые 5 МБ файла (сам по себе это будет недействительный xml, так как в нем не будет конечных тегов) - будет ли способ проанализировать схему из этого?


Обновление: Я включил пример фрагмента XML здесь: https://hastebin.com/uyalicihow.xml. Я ищу, чтобы извлечь что-то вроде фрейма данных (или списка или любой другой структуры данных, которую вы хотитеиспользовать) аналогично следующему:

Items/Item/Main/Platform       Items/Item/Info/Name
iTunes                         Chuck Versus First Class
iTunes                         Chuck Versus Bo

Как это можно сделать?Я добавил вознаграждение, чтобы поощрять ответы здесь.

Ответы [ 6 ]

0 голосов
/ 08 января 2019

На мой взгляд, ваш вопрос очень ясен.Я даю ему плюс один голос за ясность.Вы хотите разобрать текст.

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

Это может быть легко в Windows начиная с 98SE.В других операционных системах это должно быть легко.

Процесс (1) Настроить (вручную или с помощью программы), как вы сейчас делаете, мы можем вызвать это EditorA, которое редактирует ваш XML-документ, и сохранить его.;(2) остановить EditorA;(3) Запустите анализатор или редактор EditorB для сохраненного XML-документа вручную или автоматически (запускается путем обнаружения изменения XML-документа по дате, времени или размеру и т. Д.);(4) Используя EditorB, сохраните вручную или автоматически правки, начиная с шага 3;(5) Попросите вашего EditorA перезагрузить XML-документ и продолжить с него;(6) Делайте это так часто, как это необходимо, внося изменения в EditorA и автоматически настраивая их вне EditorA, используя EditorB.

Редактируйте этот способ перед отправкой файла.

Этомного печатать, чтобы объяснить, но XML это просто прославленный текстовый документ.Он может быть легко проанализирован, отредактирован и сохранен, либо символ за символом, либо большим количеством строк, строк или строк.

В качестве дальнейшего примечания это может быть применено ко всем документам, содержащимся в каталоге, или к документам всей системы, какЯ сделал в прошлом.

Убедитесь, что EditorA остановлен, прежде чем EditorB разрешено начать его изменение.Затем остановите EditorB перед перезапуском EditorA.Если вы установите это, как я описал, то EditorB можно будет непрерывно запускать в фоновом режиме, но вставьте в него автоматический уведомитель (возможно, окно сообщения с параметрами или маленькую кнопку, которая устанавливается на экране при активации), которая позволяетВы должны выключить (продолжить с помощью) EditorA перед использованием EditorB.Или, как я бы это сделал, вставьте детектор, чтобы не дать EditorB выполнять свои собственные правки, пока EditorA работает.

B Lean

0 голосов
/ 23 декабря 2018

Для очень больших файлов чтение всегда является проблемой.Я бы предложил простое алгоритмическое поведение для чтения самого файла.Ключевым моментом всегда является xml tags внутри файлов.Я бы посоветовал вам прочитать теги xml и отсортировать их внутри heap, а затем соответственно проверить содержимое heap.

Чтение файла также должно происходить по частям:

import xml.etree.ElementTree as etree
for event, elem in etree.iterparse(xmL, events=('start', 'end', 'start-ns', 'end-ns')):
  store_in_heap(event, element)

Это будет анализировать файл XML по частям за раз и давать его вам на каждом этапе пути.start сработает при первом обнаружении тега.В этот момент элемент будет пустым, за исключением elem.attrib, который содержит свойства тега.end сработает при обнаружении закрывающего тега и прочтении всего, что находится между ними.

Вы также можете воспользоваться namespaces в start-ns и end-ns.ElementTree предоставил этот вызов для сбора всех пространств имен в файле.Обратитесь к этой ссылке для получения дополнительной информации о пространствах имен

0 голосов
/ 23 декабря 2018

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

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

  2. Вы ожидаете, что эти наборы вещей будут иметь одинаковое количество значений.

  3. Вам нужночтобы иметь возможность анализировать частичные файлы.

  4. Вы не беспокоитесь о свойствах элементов, а только об их содержимом.

Я использую xml.sax, поскольку это касаетсяпроизвольно большие файлы и не нужно читать весь файл в память.Обратите внимание, что стратегия, которой я сейчас следую, на самом деле не так хорошо масштабируется, так как я храню все элементы в памяти для построения кадра данных, но вы также можете вывести пути и содержимое.

InВ примере файла существует проблема с наличием одной строки на Item, поскольку есть кратные значения тега Genre, а также несколько тегов Product.Я обработал повторяющиеся теги Genre, добавив их.Это зависит от тегов жанра, появляющихся последовательно.Не совсем понятно, как отношения Product могут обрабатываться в одной таблице.

import xml.sax

from collections import defaultdict

class StructureParser(xml.sax.handler.ContentHandler):
    def __init__(self):
        self.text = ''
        self.path = []
        self.datalist = defaultdict(list)
        self.previouspath = ''

    def startElement(self, name, attrs):
        self.path.append(name)

    def endElement(self, name):
        strippedtext = self.text.strip()
        path = '/'.join(self.path)
        if strippedtext != '':
            if path == self.previouspath:
                # This handles the "Genre" tags in the sample file
                self.datalist[path][-1] += f',{strippedtext}'
            else:
                self.datalist[path].append(strippedtext)
        self.path.pop()
        self.text = ''
        self.previouspath = path

    def characters(self, content):
        self.text += content

Вы бы использовали это так:

parser = StructureParser()

try:
    xml.sax.parse('uyalicihow.xml', parser)
except xml.sax.SAXParseException:
    print('File probably ended too soon')

Это будет читатьфайл примера просто в порядке.

Как только он прочитает и, вероятно, напечатает «Файл, вероятно, скоро закончится», вы проанализируете содержимое в parser.datalist.

Вы, очевидно, хотите иметь только те части, которые успешно читаются, поэтому вы можете составить кратчайший список и построить DataFrame только с этими путями:

import pandas as pd

smallest_items = min(len(e) for e in parser.datalist.values())
df = pd.DataFrame({key: value for key, value in parser.datalist.items() if len(value) == smallest_items})

Это дает что-то похожее на вашжелаемый результат:

  Items/Item/Main/Platform Items/Item/Main/PlatformID Items/Item/Main/Type 
0                   iTunes                  353736518            TVEpisode   
1                   iTunes                  495275084            TVEpisode   

Столбцы для тестового файла, которые соответствуют здесь,

>> df.columns
Index(['Items/Item/Main/Platform', 'Items/Item/Main/PlatformID',
       'Items/Item/Main/Type', 'Items/Item/Main/TVSeriesID',
       'Items/Item/Info/BaseURL', 'Items/Item/Info/EpisodeNumber',
       'Items/Item/Info/HighestResolution',
       'Items/Item/Info/LanguageOfMetadata', 'Items/Item/Info/LastModified',
       'Items/Item/Info/Name', 'Items/Item/Info/ReleaseDate',
       'Items/Item/Info/ReleaseYear', 'Items/Item/Info/RuntimeInMinutes',
       'Items/Item/Info/SeasonNumber', 'Items/Item/Info/Studio',
       'Items/Item/Info/Synopsis', 'Items/Item/Genres/Genre',
       'Items/Item/Products/Product/URL'],
      dtype='object')

На основании ваших комментариев кажется, что для вас более важно иметь всеэлементы представлены, но, возможно, просто показывают предварительный просмотр, и в этом случае вы можете использовать только первые элементы из данных.Обратите внимание, что в этом случае записи Product s не будут соответствовать записям Item.

df = pd.DataFrame({key: value[:smallest_items] for key, value in parser.datalist.items()}) 

Теперь мы получаем все пути:

>> df.columns
Index(['Items/Item/Main/Platform', 'Items/Item/Main/PlatformID',
       'Items/Item/Main/Type', 'Items/Item/Main/TVSeriesID',
       'Items/Item/Info/BaseURL', 'Items/Item/Info/EpisodeNumber',
       'Items/Item/Info/HighestResolution',
       'Items/Item/Info/LanguageOfMetadata', 'Items/Item/Info/LastModified',
       'Items/Item/Info/Name', 'Items/Item/Info/ReleaseDate',
       'Items/Item/Info/ReleaseYear', 'Items/Item/Info/RuntimeInMinutes',
       'Items/Item/Info/SeasonNumber', 'Items/Item/Info/Studio',
       'Items/Item/Info/Synopsis', 'Items/Item/Genres/Genre',
       'Items/Item/Products/Product/URL',
       'Items/Item/Products/Product/Offers/Offer/Price',
       'Items/Item/Products/Product/Offers/Offer/Currency'],
      dtype='object')
0 голосов
/ 22 декабря 2018

Вопрос : способ анализа структуры основного узла элемента без предварительного знания его структуры

Этот class TopSequenceElement анализ XML файла для поискавсе элементы последовательности .
Значение по умолчанию равно break при ПЕРВОМ закрытии </...> самого верхнего элемента.
Следовательно, оно независиморазмера файла или даже усеченными файлами.

from lxml import etree
from collections import OrderedDict

class TopSequenceElement(etree.iterparse):
    """
    Read XML File
    results: .seq == OrderedDict of Sequence Element
             .element == topmost closed </..> Element
             .xpath == XPath to top_element
    """
    class Element:
        """
        Classify a Element
        """
        SEQUENCE = (1, 'SEQUENCE')
        VALUE = (2, 'VALUE')

        def __init__(self, elem, event):
            if len(elem):
                self._type = self.SEQUENCE
            else:
                self._type = self.VALUE

            self._state = [event]
            self.count = 0
            self.parent = None
            self.element = None

        @property
        def state(self):
            return self._state

        @state.setter
        def state(self, event):
            self._state.append(event)

        @property
        def is_seq(self):
            return self._type == self.SEQUENCE

        def __str__(self):
            return "Type:{}, Count:{}, Parent:{:10} Events:{}"\
                .format(self._type[1], self.count, str(self.parent), self.state)

    def __init__(self, fh, break_early=True):
        """
        Initialize 'iterparse' only to callback at 'start'|'end' Events

        :param fh: File Handle of the XML File
        :param break_early: If True, break at FIRST closing </..> of the topmost Element
                            If False, run until EOF
        """
        super().__init__(fh, events=('start', 'end'))
        self.seq = OrderedDict()
        self.xpath = []
        self.element = None

        self.parse(break_early)

    def parse(self, break_early):
        """
        Parse the XML Tree, doing
          classify the Element, process only SEQUENCE Elements
          record, count of end </...> Events, 
                  parent from this Element
                  element Tree of this Element

        :param break_early: If True, break at FIRST closing </..> of the topmost Element
        :return: None
        """
        parent = []

        try:
            for event, elem in self:
                tag = elem.tag
                _elem = self.Element(elem, event)

                if _elem.is_seq:
                    if event == 'start':
                        parent.append(tag)

                        if tag in self.seq:
                            self.seq[tag].state = event
                        else:
                            self.seq[tag] = _elem

                    elif event == 'end':
                        parent.pop()
                        if parent:
                            self.seq[tag].parent = parent[-1]

                        self.seq[tag].count += 1
                        self.seq[tag].state = event

                        if self.seq[tag].count == 1:
                            self.seq[tag].element = elem

                        if break_early and len(parent) == 1:
                            break

        except etree.XMLSyntaxError:
            pass

        finally:
            """
            Find the topmost completed '<tag>...</tag>' Element
            Build .seq.xpath
            """
            for key in list(self.seq):
                self.xpath.append(key)
                if self.seq[key].count > 0:
                    self.element = self.seq[key].element
                    break

            self.xpath = '/'.join(self.xpath)

    def __str__(self):
        """
        String Representation of the Result 
        :return: .xpath and list of .seq
        """
        return "Top Sequence Element:{}\n{}"\
            .format( self.xpath,
                     '\n'.join(["{:10}:{}"
                               .format(key, elem) for key, elem in self.seq.items()
                                ])
                     )

if __name__ == "__main__":
    with open('../test/uyalicihow.xml', 'rb') as xml_file:
        tse = TopSequenceElement(xml_file)
        print(tse)

Вывод :

Top Sequence Element:Items/Item
Items     :Type:SEQUENCE, Count:0, Parent:None       Events:['start']
Item      :Type:SEQUENCE, Count:1, Parent:Items      Events:['start', 'end', 'start']
Main      :Type:SEQUENCE, Count:2, Parent:Item       Events:['start', 'end', 'start', 'end']
Info      :Type:SEQUENCE, Count:2, Parent:Item       Events:['start', 'end', 'start', 'end']
Genres    :Type:SEQUENCE, Count:2, Parent:Item       Events:['start', 'end', 'start', 'end']
Products  :Type:SEQUENCE, Count:1, Parent:Item       Events:['start', 'end']
... (omitted for brevity)

Шаг 2 : Теперь вы знаете, что есть тег <Main>, который вы можете сделать:

print(etree.tostring(tse.element.find('Main'), pretty_print=True).decode())

<Main>
      <Platform>iTunes</Platform>
      <PlatformID>353736518</PlatformID>
      <Type>TVEpisode</Type>
      <TVSeriesID>262603760</TVSeriesID>
    </Main>

Шаг 3 : Теперь вы знаете, что есть тег <Platform>, который вы можете сделать:

print(etree.tostring(tse.element.find('Main/Platform'), pretty_print=True).decode())

<Platform>iTunes</Platform>

Протестировано с Python: 3.5.3 - lxml.etree: 3.70,1

0 голосов
/ 05 декабря 2018

Несколько человек неверно истолковали этот вопрос, и, перечитывая его, на самом деле это не совсем понятно.На самом деле есть несколько вопросов.

Как определить схему XML

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

Каким будет самый быстрый способ анализа структуры узла основного элемента без предварительного знания его структуры?

Просто поместите его через анализатор, например, SAX-анализатор.Синтаксическому анализатору не нужно знать структуру XML-файла, чтобы разделить его на элементы и атрибуты.Но я не думаю, что вы действительно хотите максимально быстрый анализ (на самом деле, я не думаю, что производительность настолько высока в вашем списке требований).Я думаю, что вы хотите сделать что-то полезное с информацией (вы не сказали нам, что): то есть вы хотите обрабатывать информацию, а не просто анализировать XML.

Существует ли утилита python, которая может делать это «на лету», не загружая в память полный xml?

Да, согласно этой странице, котораяупоминает 3 парсера XML на основе событий в мире Python: https://wiki.python.org/moin/PythonXml (я не могу поручиться за них)

что делать, если я только что сохранил первые 5 МБ файла (сам по себе это был бы недействительный xml, поскольку в нем не было бы конечных тегов) - будет ли способ отобрать схему из этого?

Я не уверен, что вы знаете, что такое глагол«разобрать» на самом деле означает.Ваша фраза определенно предполагает, что вы ожидаете, что файл будет содержать схему, которую вы хотите извлечь.Но я совсем не уверен, что вы действительно это имеете в виду.И в любом случае, если бы она содержала схему в первых 5 МБ, вы могли бы обнаружить, что она просто читала файл последовательно, не было бы необходимости «сначала» сохранять первую часть файла.

0 голосов
/ 04 декабря 2018

Существует множество инструментов, которые генерируют схему из предоставленного экземпляра документа.Я не знаю, сколько из них будет работать с входным файлом 5 Гб, и я не знаю, сколько из них может быть вызвано из Python.

Много лет назад я написал полностью на основе Javaинструмент streamable для генерации DTD из экземпляра документа.Это не затрагивалось годами, но все равно должно работать: https://sourceforge.net/projects/saxon/files/DTDGenerator/7.0/dtdgen7-0.zip/download?use_mirror=vorboss

Здесь перечислены другие инструменты: Есть ли какие-либо инструменты для создания схемы XSD из экземпляра документа XML?

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