我需要在ruby on rails上解析一个xml文件,我使用nokogiri gem来解析它。
我可以这样解析以显示家长和他的孩子,但它看起来是这样的:
PARENT: Example Parent 1
CHILD: Example Children 1Example Children 2Example Children 3
PARENT: Example Parent 2
CHILD:
为什么它缺少第二个父节点的子节点?如果我用一个for each调用数组,它将显示所有子项。我是这样做的:
在控制器中:
@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 %>
如何将父母与子女"联系起来"?呈现父母和他的孩子,然后是其他父母和孩子。。。
这是我的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个值。CCD_ 2等价于最后一个索引+1(对于只有[0] .count
的数组。您的@parentN
已分配给对象
建议(简单)
使用单个数组来保存嵌套值(作为哈希),而不是两个变量。
#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')
用于选择父节点,并且可以保持不变。我们希望将@codes
键设置为嵌入Node中的字符串的实际值,该字符串不是parent.xpath('@name')
,而是实际的parent.xpath('@name')[0].value
。可能有多个具有属性"name"的节点的xml表示,我们想要第一个([0]
)。name属性的值使用.value
方法返回。
创建一个类,使节点成为对象
父节点有名称和颜色,子节点有名称、颜色和级别。看起来你有一个Node的模型,看起来像:
class Node
include ActiveModel::Model
attr_accessor :name, :color, :rank, :children
end
我在这里不使用持久性来简化事情,但你可能想把你的记录保存到磁盘上,如果你确实研究了ActiveRecord在RailsGuides 上所做的大量事情
现在,当我们浏览xml文档时,我们将创建一个对象数组,而不是字符串散列(这两个对象恰好都是对象,但我将把这个问题留给您查看)。
解析Xpath以获取节点对象的属性
设置父对象的名称和颜色属性的快速方法如下所示:
@node = Node.new(doc.xpath('Report/Node').first.attributes.inject({}) { |attrs, value| attrs[value[0].to_sym] = value[1].value; attrs })
好吧,也许这并不那么容易。我们所做的是获取XPath的可枚举结果,导航到第一个属性,并对字符串属性名称(名称、颜色、排名)及其相应值进行散列。一旦我们有了散列,我们就把它传递给Node类的新方法来实例化(创建)一个节点。这将传递给我们一个我们可以使用的对象:
@node.name
#=> "Example Parent 1"
为孩子们扩展课堂
一旦我们有了父节点,我们就可以给它子节点,在数组中创建新节点。为了便于实现这一点,我们扩展了模型的定义,以包括一个重写的初始值设定项(new())。
class Node
include ActiveModel::Model
attr_accessor :name, :color, :rank, :children
def initialize(*args)
self.children = []
super(*args)
end
end
添加子项@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
创建Node对象,并使用.first
创建它的子对象和前面的枚举,我们就可以自动化这个过程了。
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 %>