Как разобрать большой файл XML по Oga в Ruby? - PullRequest
0 голосов
/ 03 мая 2020

Я пытаюсь проанализировать большой файл XML с помощью Oga. До сих пор мне удавалось анализировать до 1,5 Гб с помощью следующего сценария, но когда я выбрал файл размером 5,6 Гб, память, используемая Ruby, стала огромной (более 50 Гб), и я не мог получить результат разбора даже в 3 дня. Я использую MacOS 10.15.4, ruby 2.7.0, память 16Gb. Я знаю, что есть и другие способы использования разных самоцветов, включая Nokogiri, но если возможно, я бы хотел знать, как это сделать в Oga.

require 'oga'
Dir.chdir __dir__

file_n = "uniprot_sprot.xml"

xml = File.open(file_n)
puts "opened data file"
document = Oga.parse_xml(xml)
puts "parsed all data"

document.xpath('uniprot/entry').each do |entry|
  ...
end

1 Ответ

0 голосов
/ 04 мая 2020

Вы можете использовать парсер стиля SAX. Так как парсеры SAX не создают документ из XML, они полезны для анализа больших документов. Недостатком является то, что вам нужно будет отслеживать состояние самостоятельно. Я никогда не использовал OGA для разбора SAX, но я предполагаю, что он подойдет для ваших 5 ГБ XML.

Вот отдельный пример. Просто вставьте его в файл и запустите (часть после __END__ будет доступна в качестве ввода, в DATA).

require "oga"

class PeopleHandler
  PERSON_PATH = ["xml", "people", "person"]
  ATTRIBUTE_PATH = ["xml", "people", "person", "attribute"]
  attr_reader :people

  def initialize
    @people = []
    @current_person = nil
    @current_path = []
  end

  def on_element(_namespace, name, attrs = {})
    current_path.push(name)
    if current_path == PERSON_PATH
      people.push({id: attrs["id"]})
    elsif current_path == ATTRIBUTE_PATH
      people.last[attrs["name"]] = attrs["value"]
    end
  end

  def after_element(_namespace, name)
    current_path.pop
  end

  private

  attr_reader :current_path, :current_person
end

handler = PeopleHandler.new

Oga.sax_parse_xml(handler, DATA.read)

p handler.people

# [{:id=>"12", "first-name"=>"Pascal", "country"=>"Switzerland"}, {:id=>"13", "first-name"=>"Fred", "country"=>"Sweden"}, {:id=>"45", "first-name"=>"Karl", "country"=>"Hungary"}]

__END__
<xml>
  <people>
    <person id="12">
      <attribute name="first-name" value="Pascal" />
      <attribute name="country" value="Switzerland" />
    </person>
    <person id="13">
      <attribute name="first-name" value="Fred" />
      <attribute name="country" value="Sweden" />
    </person>
    <person id="45">
      <attribute name="first-name" value="Karl" />
      <attribute name="country" value="Hungary" />
    </person>
</xml>

Sax-парсеры работают, отправляя события в обработчик. Смотрите список доступных событий (методы, которые вызываются) здесь: https://github.com/YorickPeterse/oga/blob/master/lib/oga/xml/sax_parser.rb

В примере используется массив (current_path) для отслеживания позиции внутри документа. Возможно, это не требуется в вашем случае, и достаточно имени элемента.

Если достигнут элемент <person>, я добавляю sh Ха sh в мой список людей. Затем для каждого элемента <attribute> я увеличиваю это ha sh (people.last) несколькими парами ключ / значение. После того, как синтаксический анализ завершен, у меня есть список людей handler.people, которые я могу обработать дальше.

Это только для того, чтобы дать вам пример того, как работают SAX-парсеры.

  • Возможно, вы делаете не нужно отслеживать путь, возможно, имя элемента достаточно хорошее (т. е. когда ваш элемент имеет уникальное имя). Тогда вы можете избежать отслеживания позиции в массиве.
  • Может быть, вы не хотите создавать коллекцию элементов для дальнейшей обработки. Возможно, вы торгуете памятью, сохраненной с помощью SAX-парсера, для памяти, которая вам нужна для ваших предметов. Вместо этого вы можете захотеть обработать элемент, когда у вас есть вся необходимая информация (вероятно, в after_element), а затем выбросить его.

Если вы хотите синхронизировать различные разделы вашего кода, вы можете использовать простое решение:

Сроки можно сделать довольно просто, чтобы получить представление:

t1 = Time.now
operation_1
t2 = Time.now
operation_2
t3 = Time.now
puts "Operation 1 took: #{t2 - t1}"
puts "Operation 2 took: #{t3 - t2}"
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...