这是我的sample.xml:
<?xml version="1.0" encoding="utf-8"?>
<ShipmentRequest>
<Message>
<Header>
<MemberId>MID-0000001</MemberId>
<MemberName>Bruce</MemberName>
<DeliveryId>0000001</DeliveryId>
<OrderNumber>ON-000000001</OrderNumber>
<ShipToName>Alan</ShipToName>
<ShipToZip>123-4567</ShipToZip>
<ShipToStreet>West</ShipToStreet>
<ShipToCity>Seatle</ShipToCity>
<Payments>
<PayType>Credit Card</PayType>
<Amount>20</Amount>
</Payments>
<Payments>
<PayType>Points</PayType>
<Amount>22</Amount>
</Payments>
<PayType />
</Header>
<Line>
<LineNumber>3.1</LineNumber>
<ItemId>A-0000001</ItemId>
<Description>Apple</Description>
<Quantity>2</Quantity>
<UnitCost>5</UnitCost>
</Line>
<Line>
<LineNumber>4.1</LineNumber>
<ItemId>P-0000001</ItemId>
<Description>Peach</Description>
<Quantity>4</Quantity>
<UnitCost>6</UnitCost>
</Line>
<Line>
<LineNumber>5.1</LineNumber>
<ItemId>O-0000001</ItemId>
<Description>Orange</Description>
<Quantity>2</Quantity>
<UnitCost>4</UnitCost>
</Line>
</Message>
</ShipmentRequest>
还有我的示例.rb:
#!/usr/bin/ruby -w
require 'nokogiri'
doc = Nokogiri::XML(open("sample.xml"))
doc.xpath("//ShipmentRequest").each {
|node| puts node.text
}
我得到的结果是:
MID-0000001
Bruce
0000001
ON-000000001
Alan
123-4567
West
Seatle
Credit Card
20
Points
22
3.1
A-0000001
Apple
2
5
4.1
P-0000001
Peach
4
6
5.1
O-0000001
Orange
2
4
我还想打印标签名称并跳过空白值的标签/节点:
MemberID: MID-0000001
MemberName: Bruce
DeliveryId: 0000001
OrderNumber: ON-000000001
ShipToName: Alan
ShipToZip: 123-4567
ShipToStreet: West
etc...
您基本上需要所有的叶元素。您可以在一个XPath表达式中捕获所有这些表达式:
leaves = doc.xpath('//*[not(*)]')
leaves.each do |node|
puts "#{node.name}: #{node.text}" unless node.text.empty?
end
输出:
MemberId: MID-0000001
MemberName: Bruce
DeliveryId: 0000001
OrderNumber: ON-000000001
ShipToName: Alan
ShipToZip: 123-4567
ShipToStreet: West
ShipToCity: Seatle
PayType: Credit Card
Amount: 20
PayType: Points
Amount: 22
LineNumber: 3.1
ItemId: A-0000001
Description: Apple
Quantity: 2
UnitCost: 5
LineNumber: 4.1
ItemId: P-0000001
Description: Peach
Quantity: 4
UnitCost: 6
LineNumber: 5.1
ItemId: O-0000001
Description: Orange
Quantity: 2
UnitCost: 4
XPath解释
XPath //*[not(*)]
查找所有叶元素。它是怎么做到的?让我们来分解一下:
//
表示扫描整个文档*
表示任何元素,因此//*
匹配文档中的所有元素[]
中的部分被称为谓词,它约束前面的表达式。我读起来像"那样"。它的作用域是元素的子元素,因此例如a[b]
表示所有a
元素,使得它们具有b
子元素not()
只是一个布尔否定,所以not(*)
的意思是"没有元素",所以在谓词中它的含义是"没有子元素">
把所有这些放在一起,就有了"文档中的所有元素,这样它们就没有任何子元素"==叶元素。
另一个版本
在注释中,@Phrogz做了一个很好的添加,通过添加另一个谓词将检查元素是否为空的逻辑移到XPath表达式中。这有两个好处:
- 它将提高性能,因为它不会返回所有叶子,然后检查它们。这可能在大型文档中或在有大量空页的情况下会很明显
- 它变成了一句俏皮话
puts doc.xpath('//*[not(*)][text()]').map{ |n| "#{n.name}: #{n.text}" }
意思是"没有子元素,但至少有一个子文本节点的每个元素。">
doc = Nokogiri::XML(File.open("sample.xml"))
doc.xpath("//ShipmentRequest/Message/Header").each do |row|
row.elements.each do |e|
next if e.text.to_s.empty?
if e.name.match(/Payments/)
e.elements.each do |ie|
puts "#{ie.name} : #{ie.text}"
end
else
puts "#{e.name} : #{e.text}"
end
end
end
doc.xpath("//ShipmentRequest/Message/Line").each do |row|
row.elements.each do |e|
next if e.text.to_s.empty?
puts "#{e.name} : #{e.text}"
end
end
输出
MemberId : MID-0000001
MemberName : Bruce
DeliveryId : 0000001
OrderNumber : ON-000000001
ShipToName : Alan
ShipToZip : 123-4567
ShipToStreet : West
ShipToCity : Seatle
PayType : CreditCard
Amount : 20
PayType : Points
Amount : 22
LineNumber : 3.1
ItemId : A-0000001
Description : Apple
Quantity : 2
UnitCost : 5
LineNumber : 4.1
ItemId : P-0000001
Description : Peach
Quantity : 4
UnitCost : 6
LineNumber : 5.1
ItemId : O-0000001
Description : Orange
Quantity : 2
UnitCost : 4