Почему поиск 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]')
Другие вопросы по тегам