使用 nokogiri 解析 xml 文件

Parse xml file with nokogiri

我需要在 rails 上解析 ruby 上的 xml 文件,我正在使用 nokogiri gem 来解析它。

我可以这样解析以显示 parent 和他的 children,但它的显示如下:

PARENT: Example Parent 1

CHILD: Example Children 1Example Children 2Example Children 3

PARENT: Example Parent 2

CHILD:

为什么它缺少第二个 parent 节点的 children?如果我用 for each 调用数组,它会显示所有 children。我是这样做的:

在控制器中:

  @codes = []
    doc.xpath('//Node').each do |parent| 
       @parentN =parent.xpath('///ancestor::*/@name')


      @codes << parent.xpath('Node/@name').text

    end

和视图:

<% for x in 0...@parentN.count %>

    <p> PARENT: <%= @parentN[x]  %>  </p>

  <p> CHILD:  <%= @codes[x] %>  </p>

    <%   end %>

我怎样才能"connect"和孩子们一起parent?介绍 parent 和他的 children,然后介绍其他 parent 和 children...

这是我的 xml 文件:

   <Report>
       <Node name="Example Parent 1" color="red">
          <Node name="Example Children 1" color="red" rank="very high" />
          <Node name="Example Children 2" color="red" rank="high" />
          <Node name="Example Children 3" color="yellow" rank="moderate" />
       </Node>
       <Node name="Example Parent 2" color="yellow">
          <Node name="Example Children 1" color="yellow" rank="moderate" />
       </Node>
    </Report>

问题 #1

在这一行中:

       @parentN =parent.xpath('///ancestor::*/@name')

您覆盖了 @parentN 的先前值。

问题 #2

来自 运行

<% for x in 0...@parentN.count %>

您将获得单值数组的 2 个值。 .count 相当于最后一个索引 +1(对于只有 [0] .count 的数组是 1。您的 @parentN 被分配给 object

推荐(简单)

使用单个数组来保存嵌套值(作为散列)而不是两个变量。

#xmlController.rb
@codes = []
doc.xpath('Report/Node').each do |parent| 
  @codes << { parent.xpath('@name') => parent.xpath('Node').map { |child| child.text }
end



#show.html.erb

<% @codes.each do |parent, children| %>
  <p> PARENT: <%= @parent  %>  </p>
  <p> CHILDREN:  <%= @children.each { |child| p child } %>  </p>

基于以下评论的推荐

上面显示的是最简单的思考问题的方法。现在我们已准备好解析节点中的所有数据,我们需要更改我们的 xpath 和地图。 doc.xpath('Report/Node')用于selectparent节点,可以保持不变。我们希望将 @codes 键设置为嵌入在节点中的字符串的实际值,该值不是 parent.xpath('@name'),而是 parent.xpath('@name')[0].value。具有属性 'name' 的节点可能有多个 xml 表示,我们想要第一个 ([0]) 表示。使用 .value 方法返回名称属性的值。

做一个class这样节点就变成objects

您的 Parent 节点有名称和颜色,您的 children 有名称、颜色和等级。看起来您的 Node 模型如下所示:

class Node
  include ActiveModel::Model
  attr_accessor :name, :color, :rank, :children
end

我在这里不使用持久性来简化事情,但您可能希望将记录保存到磁盘,如果您确实研究了 ActiveRecord 在 RailsGuides

上所做的一系列事情

现在,当我们浏览 xml 文档时,我们将创建一个 object 的数组,而不是字符串的散列(它们都恰好是 object,但是我会把这个问题留给你检查。

解析 Xpath 获取节点的属性 Objects

设置 parent 的名称和颜色属性的快速方法如下所示:

@node = Node.new(doc.xpath('Report/Node').first.attributes.inject({}) { |attrs, value| attrs[value[0].to_sym] = value[1].value; attrs })

好吧,也许这并不是那么容易。我们所做的是获取 XPath 的 Enumerable 结果,导航到第一个属性并生成字符串属性名称(名称、颜色、等级)及其对应值的散列。一旦我们有了哈希,我们就将它传递给我们的 Node class' 新方法来实例化(创建)一个节点。这将传递给我们一个 object 我们可以使用:

@node.name
#=> "Example Parent 1"

延长 Class 为 children

一旦我们有了 parent 节点,我们就可以给它 children,在数组中创建新节点。为了促进这一点,我们扩展了模型的定义以包含一个重写的初始化程序 (new())。

class Node
  include ActiveModel::Model
  attr_accessor :name, :color, :rank, :children

  def initialize(*args)
    self.children = []
    super(*args)
  end
end
添加 children
@node.children << Node.new(doc.xpath('Report/Node').first.xpath('Node').first.attributes.inject({}) { |attrs, value| attrs[value[0].to_sym] = value[1].value; attrs })

既然我们知道如何使用 .first 创建一个节点 object 以及使用 .first 和前面的枚举创建它的 child ,我们就可以自动化这个过程。

doc.xpath('Report/Node').each do |parent|
  node = Node.new(parent.attributes.inject({}) { |attrs, value| attrs[value[0].to_sym] = value[1].value; attrs }))
  node.children = parent.xpath('Node').map do |child|
    Node.new(child.attributes.inject({}) { |attrs, value| attrs[value[0].to_sym] = value[1].value; attrs }))
  end
end

丑陋的控制器代码

将其移动到模型

但是等等!那不是很干!让我们将伤害我们眼睛的逻辑移到模型中以使其更易于使用。

class Node
  include ActiveModel::Model
  attr_accessor :name, :color, :rank, :children

  def initialize(*args)
    self.children = []
    super(*args)
  end

  def self.new_from_xpath(xml_node)
    self.new(xml_node.attributes.inject({}) { |attrs, value| attrs[value[0].to_sym] = value[1].value; attrs })
  end
end

最终控制者

现在控制器看起来像这样:

@nodes = []
doc.xpath('Report/Node').each do |parent|
  node = Node.new_from_xpath(parent)
  node.children = parent.xpath('Node').map do |child|
    Node.new_from_xpath(child)
  end
  @nodes << node
end

在视图中使用它

在视图中,您可以像这样使用@nodes:

<% for @node in @nodes %>
  Parent: <%= @node.name %>
  Children: <% for @child in @node.children %>
    <%= @child.name %> is <%= @child.color %>
  <% end %>
<% end %>