我使用的是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>
更新
我切换到Nokogiri:
我的请求(localhost)已从4秒下降到250秒。我的XML文件是5.6MB。由于这可能对其他人有帮助,我将我的更改粘贴在下面:
class IncidentsController < ApplicationController
require 'nokogiri'
# ....
def vehicle
# helpfull links: =============================================================================
# Some say Nokogire is best: http://nokogiri.org/
# recursive link: http://stackoverflow.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] =~ /^[dw]{6}$/
num = 'pa_number'
elsif params[:id] =~ /^[dw]{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的新手,所以欢迎对我的代码发表进一步的评论!
Hpricot在首次出现时非常出色,因为它将CSS选择器语法引入了HTML解析。然而,它从来都不是完全符合XPath的,特别是在您正在使用的XPath谓词语法方面。
我建议野村。这个库快速且维护良好,并且完全符合XPath1.0。有了它,你应该能够拉动车辆:
doc.search('//vehicle[line_number[text()=1234]]')
另外,稍微简化一下:您确实不需要嵌套谓词。这也将识别正确的车辆:
doc.search('//vehicle[line_number/text()=1234]')