Почему поиск xPath работает в REXML, а не в Hpricot?
Я использую Rails 3.2 и Hpricot.
Я хотел бы найти элемент XML по содержимому его дочернего элемента и преобразовать его в объект Ruby, который позже будет отображен.
Другими словами, я хотел бы найти ‘vehicle’
элемент, где его ребенок ‘line_number’
содержание равно 1234
,
Это работало нормально с REXML и следующим xPath:
/gsip/vehicle[line_number[text()=1234]]
REXML медленный, поэтому я переключился на Hpricot, где тот же xPath находит все элементы автомобиля, а не только тот, где ‘line_number’
равняется 1234
,
Почему это находит все транспортные средства?
file_path = Rails.root.join('public','gsip','gsip-vehicle-data.xml')
q = "/gsip/vehicle[line_number[text()=#{params[:id]}]]"
@vehicle_data = { :date => Date.today - 10.years } # initiate with very old date
xmldoc = File.read(file_path)
doc = Hpricot::XML(xmldoc)
doc.search(q) do |e|
if e.at('line_number').innerText == params[:id] # This line shouldn't be necessary?!
logger.info( "#{e.at('pa_number').innerText} (#{e.at('line_number').innerText} from #{e.at('date').innerText})" )
vehicle_date = Date.strptime(e.at('date').innerText, "%d.%m.%Y")
#logger.info('date: ' + vehicle_date.to_s)
if vehicle_date > @vehicle_data[:date]
e.children.select do |n|
logger.info("#{n.name} = #{n.innerText}")
@vehicle_data[n.name] = n.innerText
end
end
end
end
Это находит искомый автомобиль, но медленно:
file_path = Rails.root.join('public','gsip','gsip-vehicle-data.xml')
q = "/gsip/vehicle[line_number[text()=#{params[:id]}]]"
@vehicle_data = { :date => Date.today - 10.years } # initiate with very old date
XPath.each(xmldoc, q ) { |e|
#find the latest vehicle with given line_number
vehicle_date = Date.strptime(XPath.first(e,'date').text, "%d.%m.%Y")
if vehicle_date > @vehicle_data[:date]
e.elements.each { |n|
@vehicle_data[n.name] = n.text
}
end
}
Мой XML:
<gsip export_date="7/25/2012 12:04:27 PM" schema_version="1.01">
<vehicle id="ABC">
<date>02.07.2012</date>
<line_number>1234</line_number>
<pa_number>ABC</pa_number>
<vin>VIN</vin>
<my>2012</my>
</vehicle>
<vehicle id="ABD">
<date>02.07.2012</date>
<line_number>8348</line_number>
<pa_number>ABD</pa_number>
<vin>VIN</vin>
<my>2012</my>
</vehicle>
<vehicle>
...
</vehicle>
...
</gsip>
ОБНОВИТЬ
Мой переход на Нокогири:
Мой запрос (localhost) снизился с 4 секунд до 250 мс. Мой XML-файл имеет размер 5,6 МБ. Поскольку это может быть полезно для других, я вставил свои изменения ниже:
class IncidentsController < ApplicationController
require 'nokogiri'
# ....
def vehicle
# helpfull links: =============================================================================
# Some say Nokogire is best: http://nokogiri.org/
# recursive link: http://stackru.com/questions/11665126/why-xpath-search-works-in-rexml-but-not-with-hpricot
# =============================================================================================
# check if PA Number or Line Number is given:
num = ''
if params[:id] =~ /^\d{4}$/
num = 'line_number'
elsif params[:id] =~ /^[\d\w]{6}$/
num = 'pa_number'
elsif params[:id] =~ /^[\d\w]{17}$/
num = 'vin'
end
# read Vehicle Data from XML File
file_path = Rails.root.join('private','gsip','gsip-vehicle-data.xml')
q = "/gsip/vehicle[#{num}/text()='#{params[:id]}']"
@vehicle_data = { :date => Date.today - 10.years } # initiate with very old date
#logger.info("*** Find Vehicle Data in XML. xPath: #{q}")
doc = Nokogiri::XML( File.read(file_path) )
doc.xpath(q).each do |e|
vehicle_date = Date.strptime(e.xpath('date').first.content, "%d.%m.%Y")
#logger.info("Date: #{vehicle_date.to_s}")
if vehicle_date > @vehicle_data[:date]
e.element_children.all? do |n|
@vehicle_data[n.name] = n.content
end
end
end
respond_to do |format|
format.html { redirect_to connectors_path }
format.json { render :json => @vehicle_data }
format.xml { render :xml => @vehicle_data }
end
end
# ...
end
Я новичок в Rails, так что дальнейшие комментарии к моему коду приветствуются!
1 ответ
Hpricot был замечательным, когда он впервые появился на сцене, потому что он ввел синтаксис CSS-селектора для разбора HTML. Однако он не был полностью совместим с XPath, особенно в отношении синтаксиса предикатов XPath, который вы используете.
Я бы предложил Nokogiri. Эта библиотека работает быстро и хорошо поддерживается, и полностью совместима с XPath 1.0. С его помощью вы сможете тянуть автомобиль:
doc.search('//vehicle[line_number[text()=1234]]')
Также небольшое упрощение: вам действительно не нужны вложенные предикаты. Это также определит правильный автомобиль:
doc.search('//vehicle[line_number/text()=1234]')