0

试图处理一些来自名为 TeleForm 的应用程序的 XML。这是表单扫描软件,它抓取数据并将其放入 XML。这是 XML 的片段

<?xml version="1.0" encoding="ISO-8859-1"?>
<Records>
  <Record>
    <Field id="ImageFilename" type="string" length="14"><Value>00000022000000</Value></Field>
    <Field id="Criterion_1" type="number" length="2"><Value>3</Value></Field>
    <Field id="Withdrew" type="string" length="1"></Field>
  </Record>

  <Record>
    <Field id="ImageFilename" type="string" length="14"><Value>00000022000001</Value></Field>
    <Field id="Criterion_1" type="number" length="2"><Value>3</Value></Field>
    <Field id="Withdrew" type="string" length="1"></Field>
  </Record>
</Records>

我已经在另一个系统中处理了这个问题,可能使用了我们编写的自定义解析器。我认为在 Rails 中不会有问题,但我错了。

用 Hash.from_xml 或 Nokogiri 解析它并没有给我预期的结果,我得到:

{"Records"=>{"Record"=>[{"Field"=>["", {"id"=>"Criterion_1", "type"=>"number", "length"=>"2", "Value"=>"3"}, ""]},
 {"Field"=>["", {"id"=>"Criterion_1", "type"=>"number", "length"=>"2", "Value"=>"3"}, ""]}]}}

在花了太多时间在这之后,我发现如果我 gsub 出类型和长度属性,我得到了我所期望的(即使它是错误的!我只在第一个记录节点上删除了)。

{"Records"=>{"Record"=>[{"Field"=>[{"id"=>"ImageFilename", "Value"=>"00000022000000"}, 
{"id"=>"Criterion_1", "type"=>"number", "length"=>"2", "Value"=>"3"}, {"id"=>"Withdrew"}]}, 
{"Field"=>["", {"id"=>"Criterion_1", "type"=>"number", "length"=>"2", "Value"=>"3"}, ""]}]}}

由于不精通 XML,我假设这种使用类型和长度属性的 XML 风格正在尝试转换为数据类型。在这种情况下,我可以理解为什么“Withdrew”属性显示为空,但不明白“ImageFilename”为什么为空——它是一个 14 个字符的字符串。

我已经解决了 gsub,但这是无效的 XML 吗?添加 DTD(TeleForm 应该提供的)会给我不同的结果吗?

编辑

我将使用一些代码作为编辑为我自己的问题提供一个可能的答案。该代码遵循了我从 Mark Thomas 那里收到的一个答案中的一些功能,但我决定反对 Nokogiri,原因如下:

  • xml 是一致的并且总是包含相同的标签(/Records/Record/Field)和属性。
  • 每个 XML 文件中可能有数百条记录,Nokogiri 似乎有点慢,只有 26 条记录
  • 我想出了如何让 Hash.from_xml 给我我期望的东西(不喜欢 type="string",但只使用哈希来填充一个类。

具有一条完整记录的 XML 扩展版本

<?xml version="1.0" encoding="ISO-8859-1"?>
<Records>
  <Record>
    <Field id="ImageFilename" type="string" length="14"><Value>00000022000000</Value></Field>
    <Field id="DocID" type="string" length="15"><Value>731192AIINSC</Value></Field>
    <Field id="FormID" type="string" length="6"><Value>AIINSC</Value></Field>
    <Field id="Availability" type="string" length="18"><Value>M  T  W  H  F  S</Value></Field>
    <Field id="Criterion_1" type="number" length="2"><Value>3</Value></Field>
    <Field id="Criterion_2" type="number" length="2"><Value>3</Value></Field>
    <Field id="Criterion_3" type="number" length="2"><Value>3</Value></Field>
    <Field id="Criterion_4" type="number" length="2"><Value>3</Value></Field>
    <Field id="Criterion_5" type="number" length="2"><Value>3</Value></Field>
    <Field id="Criterion_6" type="number" length="2"><Value>3</Value></Field>
    <Field id="Criterion_7" type="number" length="2"><Value>3</Value></Field>
    <Field id="Criterion_8" type="number" length="2"><Value>3</Value></Field>
    <Field id="Criterion_9" type="number" length="2"><Value>3</Value></Field>
    <Field id="Criterion_10" type="number" length="2"><Value>3</Value></Field>
    <Field id="Criterion_11" type="number" length="2"><Value>0</Value></Field>
    <Field id="Criterion_12" type="number" length="2"><Value>0</Value></Field>
    <Field id="Criterion_13" type="number" length="2"><Value>0</Value></Field>
    <Field id="Criterion_14" type="number" length="2"><Value>0</Value></Field>
    <Field id="Criterion_15" type="number" length="2"><Value>0</Value></Field>
    <Field id="DayTraining" type="string" length="1"><Value>Y</Value></Field>
    <Field id="SaturdayTraining" type="string" length="1"></Field>
    <Field id="CitizenStageID" type="string" length="12"><Value>731192</Value></Field>
    <Field id="NoShow" type="string" length="1"></Field>
    <Field id="NightTraining" type="string" length="1"></Field>
    <Field id="Withdrew" type="string" length="1"></Field>
    <Field id="JobStageID" type="string" length="12"><Value>2292</Value></Field>
    <Field id="DirectHire" type="string" length="1"></Field>
  </Record>
</Records>

我只是在尝试使用工作流原型来替换用 4D 和 Active4D 编写的老化系统。这个处理 TeleForms 数据的领域是作为批处理操作实现的,它仍然可以恢复到那个状态。我只是想在新的 Rails 实现中合并一些旧的可行概念。XML 文件位于共享服务器上,可能必须移动到 Web 根目录,然后设置一些触发器来处理文件。

我仍处于定义阶段,但我处理 InterviewForm 的模块/类看起来像这样并且可能会改变(几乎没有错误捕获,仍在尝试进行测试,并且我的 Ruby 在玩过之后并没有达到应有的水平Rails 大约 5 年!):

module Teleform::InterviewForm

  class Form < Prawn::Document
    # Not relevant to this question, but this class generates the forms from a Fillable PDF template and 
    # relavant Model(s) data.
    # These forms, when completed are what is processsed by TeleForms and produces the xml.
  end

  class RateForms
    attr_accessor  :records, :results

    def initialize(xml_path)
      fields = []
      xml = File.read(xml_path)
      # Hash.from_xml does not like a type of "string"
      hash = Hash.from_xml(xml.gsub(/type="string"/,'type="text"'))
      hash["Records"]["Record"].each do |record|
        #extract the field form each record
        fields << record["Field"]
      end
      @records = []
      fields.each do |field|
        #build the records for the form
        @records << Record.new(field)
      end
      @results = rate_records
    end

    def rate_records
      # not relevant to the qustions but this is where the data is processed and a bunch of stuff takes place
      return "Any errors"
    end
  end


  class Record
    attr_accessor(*[:image_filename, :doc_id, :form_id, :availability, :criterion_1, :criterion_2, 
      :criterion_3, :criterion_4, :criterion_5, :criterion_6, :criterion_7, :criterion_8, 
      :criterion_9, :criterion_10, :criterion_11, :criterion_12, :criterion_13, :criterion_14, :criterion_15, 
      :day_training, :saturday_training, :citizen_stage_id, :no_show, :night_training, :withdrew, :job_stage_id, :direct_hire])

    def initialize(fields)
      fields.each do |field|
        if field["type"] == "number"
          try("#{field["id"].underscore.to_sym}=", field["Value"].to_i)
        else
          try("#{field["id"].underscore.to_sym}=", field["Value"])
        end
      end
    end
  end

end
4

2 回答 2

0

感谢您添加附加信息,这是对受访者的评分。在您的代码中使用此域信息可能会改进它。您还没有发布任何代码,但通常使用域对象会导致更简洁和更具可读性的代码。我建议创建一个表示 的简单类Rating,而不是将数据从 XML 转换为数据结构。

class Rating
  attr_accessor :image_filename, :criterion_1, :withdrew
end

使用上面的类,这是使用 Nokogiri 从 XML 中提取字段的一种方法。

doc = Nokogiri::XML(xml)
ratings = []

doc.xpath('//Record').each do |record|
    rating = Rating.new
    rating.image_filename = record.at('Field[@id="ImageFilename"]/Value/text()').to_s
    rating.criterion_1 = record.at('Field[@id="Criterion_1"]/Value/text()').to_s
    rating.withdrew = record.at('Field[@id="Withdrew"]/Value/text()').to_s
    ratings << rating
end

现在,ratings是一个Rating对象列表,每个对象都有检索数据的方法。这比深入研究深层数据结构要干净得多。您甚至可以Rating进一步改进该类,例如创建一个withdrew?返回 true 或 false 的方法。

于 2013-02-17T01:17:01.640 回答
0

看起来XmlSimple(由 maik 编写)比不可靠且不一致的实现更适合此任务Hash.from_xml

一个久经考验的同名 perl 模块的移植,它有几个显着的优点。

  • 它是一致的,无论您发现一个节点出现一次还是多次
  • 不会阻塞和混淆结果
  • 能够区分属性和节点内容。

通过解析器运行上述相同的 xml 文档:

XmlSimple.xml_in xml

会产生以下结果。

{"Record"=>
  [{"Field"=>
     [{"id"=>"ImageFilename", "type"=>"string", "length"=>"14", "Value"=>["00000022000000"]},
      {"id"=>"DocID", "type"=>"string", "length"=>"15", "Value"=>["731192AIINSC"]},
      {"id"=>"FormID", "type"=>"string", "length"=>"6", "Value"=>["AIINSC"]},
      {"id"=>"Availability", "type"=>"string", "length"=>"18", "Value"=>["M  T  W  H  F  S"]},
      {"id"=>"Criterion_1", "type"=>"number", "length"=>"2", "Value"=>["3"]},
      {"id"=>"Criterion_2", "type"=>"number", "length"=>"2", "Value"=>["3"]},
      {"id"=>"Criterion_3", "type"=>"number", "length"=>"2", "Value"=>["3"]},
      {"id"=>"Criterion_4", "type"=>"number", "length"=>"2", "Value"=>["3"]},
      {"id"=>"Criterion_5", "type"=>"number", "length"=>"2", "Value"=>["3"]},
      {"id"=>"Criterion_6", "type"=>"number", "length"=>"2", "Value"=>["3"]},
      {"id"=>"Criterion_7", "type"=>"number", "length"=>"2", "Value"=>["3"]},
      {"id"=>"Criterion_8", "type"=>"number", "length"=>"2", "Value"=>["3"]},
      {"id"=>"Criterion_9", "type"=>"number", "length"=>"2", "Value"=>["3"]},
      {"id"=>"Criterion_10", "type"=>"number", "length"=>"2", "Value"=>["3"]},
      {"id"=>"Criterion_11", "type"=>"number", "length"=>"2", "Value"=>["0"]},
      {"id"=>"Criterion_12", "type"=>"number", "length"=>"2", "Value"=>["0"]},
      {"id"=>"Criterion_13", "type"=>"number", "length"=>"2", "Value"=>["0"]},
      {"id"=>"Criterion_14", "type"=>"number", "length"=>"2", "Value"=>["0"]},
      {"id"=>"Criterion_15", "type"=>"number", "length"=>"2", "Value"=>["0"]},
      {"id"=>"DayTraining", "type"=>"string", "length"=>"1", "Value"=>["Y"]},
      {"id"=>"SaturdayTraining", "type"=>"string", "length"=>"1"},
      {"id"=>"CitizenStageID", "type"=>"string", "length"=>"12", "Value"=>["731192"]},
      {"id"=>"NoShow", "type"=>"string", "length"=>"1"},
      {"id"=>"NightTraining", "type"=>"string", "length"=>"1"},
      {"id"=>"Withdrew", "type"=>"string", "length"=>"1"},
      {"id"=>"JobStageID", "type"=>"string", "lth"=>"12", "Value"=>["2292"]},
      {"id"=>"DirectHire", "type"=>"string", "length"=>"1"}]
  }]
}

我正在考虑解决问题并为 Hash 提供一个可行的实现,from_xml并希望从其他得出相同结论的人那里找到一些反馈。当然,我们并不是唯一遭受这些挫折的人。

与此同时,我们可能会因为知道有比它更轻的东西Nokogiri及其完整的厨房水槽来完成这项任务而感到安慰。

开心!

于 2013-09-17T23:20:36.313 回答