如何使用 Ruby nokogiri 对 XML 文件进行排序
How to sort XML file using Ruby nokogiri
我想对此 XML 进行排序,以便首先显示相同类型的受众特征 staty_type="REACH" 出现在顶部,然后是所有点击,依此类推。
这是一个示例对象:
<?xml version="1.0"?>
<properties date="2020-06-23">
<property>
<order start="2020-06-23" end="2020-06-23">52658</order>
<demographics demographic="Age" stat_type="REACH">
<value category="18-24">36</value>
<value category="25-34">149</value>
</demographics>
<demographics demographic="Age" stat_type="CLICK">
<value category="18-24">6</value>
<value category="25-34">37</value>
</demographics>
<demographics demographic="Gender" stat_type="REACH">
<value category="female">402</value>
<value category="male">188</value>
</demographics>
<demographics demographic="Gender" stat_type="CLICK">
<value category="female">107</value>
<value category="male">44</value>
</demographics>
</property>
</properties>
我可以迭代 XML。但是,无法执行排序。
@doc = Nokogiri::XML(File.open("public/test.xml"))
builder = @doc.xpath("//property")
builder.search('./demographics').sort_by{|t| puts t['stat_type']}.each do |table|
puts table.to_s
end
我需要这个表格的最终XML。
<?xml version="1.0"?>
<properties date="2020-06-23">
<property>
<order start="2020-06-23" end="2020-06-23">PBNI152658</order>
<demographics demographic="Age" stat_type="REACH">
<value category="18-24">36</value>
<value category="25-34">149</value>
</demographics>
<demographics demographic="Gender" stat_type="REACH">
<value category="female">402</value>
<value category="male">188</value>
</demographics>
<demographics demographic="Age" stat_type="CLICK">
<value category="18-24">6</value>
<value category="25-34">37</value>
</demographics>
<demographics demographic="Gender" stat_type="CLICK">
<value category="female">107</value>
<value category="male">44</value>
</demographics>
</property>
</properties>
当您执行 builder.search('./demographics')
之类的操作时,您只需创建一个 new 节点集,其中一些节点是从初始 XML 文档中筛选出来的。即使您对这个新节点集进行排序,也不会影响初始文档本身。
要对初始文档的节点进行排序,您必须重建 相关节点的子节点(在您的情况下为<property>
)。这里有一个额外的小挑战 - Nokogiri 解析了更多节点,而不仅仅是要排序的节点:
pry(main)> @doc.at_xpath("//property").children.map(&:node_name)
=> ["text", "order", "text", "demographics", "text", "demographics", "text", "demographics", "text", "demographics", "text"]
因此,我们要做的是仅对人口统计节点进行排序,并保持其他所有内容不变。其中一种方法是:
property_node = @doc.at_xpath("//property")
nodes_to_sort = property_node.children.dup
# My sorting logic is dumb here, apply your own as necessary
sorted_demographics = nodes_to_sort.select { |n| n.node_name == "demographics" }.sort_by { |n| n.attr("stat_type") }.reverse
# Create an empty nodeset. There should be a more idiomatic and readable way but this trick works too
new_nodeset = nodes_to_sort - nodes_to_sort
nodes_to_sort.each do |n|
case n.node_name
when "demographics"
new_nodeset << sorted_demographics.shift
else
new_nodeset << n
end
end
property_node.children = new_nodeset
瞧! - 我们现在排序:
pry(main)> puts @doc
<?xml version="1.0"?>
<properties date="2020-06-23">
<property>
<order start="2020-06-23" end="2020-06-23">52658</order>
<demographics demographic="Gender" stat_type="REACH">
<value category="female">402</value>
<value category="male">188</value>
</demographics>
<demographics demographic="Age" stat_type="REACH">
<value category="18-24">36</value>
<value category="25-34">149</value>
</demographics>
<demographics demographic="Gender" stat_type="CLICK">
<value category="female">107</value>
<value category="male">44</value>
</demographics>
<demographics demographic="Age" stat_type="CLICK">
<value category="18-24">6</value>
<value category="25-34">37</value>
</demographics>
</property>
</properties>
注意。对上面的解决方案持保留态度——我不太了解 nokogiri 的 XML 构建能力,所以有一些方法可以用更少的 code/in 更惯用的方式实现相同的结果。
我想对此 XML 进行排序,以便首先显示相同类型的受众特征 staty_type="REACH" 出现在顶部,然后是所有点击,依此类推。
这是一个示例对象:
<?xml version="1.0"?>
<properties date="2020-06-23">
<property>
<order start="2020-06-23" end="2020-06-23">52658</order>
<demographics demographic="Age" stat_type="REACH">
<value category="18-24">36</value>
<value category="25-34">149</value>
</demographics>
<demographics demographic="Age" stat_type="CLICK">
<value category="18-24">6</value>
<value category="25-34">37</value>
</demographics>
<demographics demographic="Gender" stat_type="REACH">
<value category="female">402</value>
<value category="male">188</value>
</demographics>
<demographics demographic="Gender" stat_type="CLICK">
<value category="female">107</value>
<value category="male">44</value>
</demographics>
</property>
</properties>
我可以迭代 XML。但是,无法执行排序。
@doc = Nokogiri::XML(File.open("public/test.xml"))
builder = @doc.xpath("//property")
builder.search('./demographics').sort_by{|t| puts t['stat_type']}.each do |table|
puts table.to_s
end
我需要这个表格的最终XML。
<?xml version="1.0"?>
<properties date="2020-06-23">
<property>
<order start="2020-06-23" end="2020-06-23">PBNI152658</order>
<demographics demographic="Age" stat_type="REACH">
<value category="18-24">36</value>
<value category="25-34">149</value>
</demographics>
<demographics demographic="Gender" stat_type="REACH">
<value category="female">402</value>
<value category="male">188</value>
</demographics>
<demographics demographic="Age" stat_type="CLICK">
<value category="18-24">6</value>
<value category="25-34">37</value>
</demographics>
<demographics demographic="Gender" stat_type="CLICK">
<value category="female">107</value>
<value category="male">44</value>
</demographics>
</property>
</properties>
当您执行 builder.search('./demographics')
之类的操作时,您只需创建一个 new 节点集,其中一些节点是从初始 XML 文档中筛选出来的。即使您对这个新节点集进行排序,也不会影响初始文档本身。
要对初始文档的节点进行排序,您必须重建 相关节点的子节点(在您的情况下为<property>
)。这里有一个额外的小挑战 - Nokogiri 解析了更多节点,而不仅仅是要排序的节点:
pry(main)> @doc.at_xpath("//property").children.map(&:node_name)
=> ["text", "order", "text", "demographics", "text", "demographics", "text", "demographics", "text", "demographics", "text"]
因此,我们要做的是仅对人口统计节点进行排序,并保持其他所有内容不变。其中一种方法是:
property_node = @doc.at_xpath("//property")
nodes_to_sort = property_node.children.dup
# My sorting logic is dumb here, apply your own as necessary
sorted_demographics = nodes_to_sort.select { |n| n.node_name == "demographics" }.sort_by { |n| n.attr("stat_type") }.reverse
# Create an empty nodeset. There should be a more idiomatic and readable way but this trick works too
new_nodeset = nodes_to_sort - nodes_to_sort
nodes_to_sort.each do |n|
case n.node_name
when "demographics"
new_nodeset << sorted_demographics.shift
else
new_nodeset << n
end
end
property_node.children = new_nodeset
瞧! - 我们现在排序:
pry(main)> puts @doc
<?xml version="1.0"?>
<properties date="2020-06-23">
<property>
<order start="2020-06-23" end="2020-06-23">52658</order>
<demographics demographic="Gender" stat_type="REACH">
<value category="female">402</value>
<value category="male">188</value>
</demographics>
<demographics demographic="Age" stat_type="REACH">
<value category="18-24">36</value>
<value category="25-34">149</value>
</demographics>
<demographics demographic="Gender" stat_type="CLICK">
<value category="female">107</value>
<value category="male">44</value>
</demographics>
<demographics demographic="Age" stat_type="CLICK">
<value category="18-24">6</value>
<value category="25-34">37</value>
</demographics>
</property>
</properties>
注意。对上面的解决方案持保留态度——我不太了解 nokogiri 的 XML 构建能力,所以有一些方法可以用更少的 code/in 更惯用的方式实现相同的结果。