我有一些xml的格式如下所示,我正在尝试使用Nokogiri :: XML :: Reader解析,因为文件大小相当大(~1GB)。该文件具有以下格式的许多packets
。
每个packet
我需要收集frame.time_epoch
,s1ap.procedureCode
。
我目前正在做以下事情。
data = []
file = `some_file.xml`
reader = Nokogiri::XML::Reader(File.open(file))
reader.each do |node|
if (node.name == 'packet' && node.node_type == Nokogiri::XML::Reader::TYPE_ELEMENT)
doc = Nokogiri::XML(node.outer_xml)
next if !doc.css("field[name='s1ap.procedureCode']") ## do nothing if the <packet> is not of s1ap type
epochTime = doc.css("field[name='frame.time_epoch']").first["show"].to_i
procedureCode = procedureCode_node = doc.css("field[name='s1ap.procedureCode']").first["show"].to_i
data << { epochTime: epochTime, procedureCode: procedureCode }
end
end
问题
我面临的挑战是解析非常缓慢。我注意到的一件事是读者扫描<packet> </packet>
中的所有后续行 - 是否有一种方法可以让读者移动到名为packet
的下一个节点,而不是通过{{1}内的每一行1}}进一步。
XML格式
packet
答案 0 :(得分:3)
对于如此庞大的文档,您应该使用SAX解析器
http://www.rubydoc.info/github/sparklemotion/nokogiri/Nokogiri/XML/SAX
流处理大型文档而不将整个内容拉入内存并最好解析为DOM。特别是考虑到问题只需要一次通过。
以下是通过使用SAX流式传输XML来完成任务的代码:
require 'nokogiri'
class PacketFilter < Nokogiri::XML::SAX::Document
def initialize
reset
end
def end_document
puts 'the document has ended'
end
def start_element(name, attributes = [])
case name
when 'packet'
@in_packet = true
when 'proto'
@have_s1ap = @in_packet && attribute_value(attributes, 'name') == 's1ap'
when 'field'
case attribute_value(attributes, 'name')
when 's1ap.procedureCode'
@procedure_code = attribute_value(attributes, 'showname')
when 'frame.time_epoch'
@epoch_time = attribute_value(attributes, 'showname')
end
end
end
def end_element(name)
if name == 'packet'
puts "#{@procedure_code}, #{@epoch_time}" if @have_s1ap
reset
end
end
private
def attribute_value(attributes, name)
attributes.reduce(nil) do |value, assoc|
assoc[0] == name ? assoc[1] : value
end
end
def reset
@in_packet = false
@have_s1ap = false
@procedure_code = nil
@epoch_time = nil
end
end
parser = Nokogiri::XML::SAX::Parser.new(PacketFilter.new)
parser.parse($stdin)
如果您将数据样本粘贴到data.xml
,将上述红宝石粘贴到slap.rb
:
$ cat data.xml | ruby poke.rb
procedureCode: id-downlinkNASTransport (11), Epoch Time: 1474267259.184197000 seconds
the document has ended
答案 1 :(得分:0)
不是循环遍历每个节点,而是只遍历packet
元素,然后跳过任何不符合条件的元素。这只会执行packet
元素而不是所有元素,这应该明显更快。
data = []
file = 'some_file.xml'
doc = Nokogiri::XML.fragment(File.read(file)) # use `read` instead of `open`
doc.xpath('packet').each do |packet|
next if !packet.css("field[name='s1ap.procedureCode']") ## do nothing if the <packet> is not of s1ap type
epochTime = packet.css("field[name='frame.time_epoch']").first["show"].to_i
procedureCode = procedureCode_node = packet.css("field[name='s1ap.procedureCode']").first["show"].to_i
data << { epochTime: epochTime, procedureCode: procedureCode }
end
» data
=> [{:epochTime=>1474267259, :procedureCode=>11}