如何使用nokogiri打印所有非空白XML节点的值及其标记名称?

Ask*_*kar 2 ruby xpath nokogiri xml-parsing

这是我的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>
Run Code Online (Sandbox Code Playgroud)

还有我的sample.rb:

#!/usr/bin/ruby -w

require 'nokogiri'

doc = Nokogiri::XML(open("sample.xml"))
doc.xpath("//ShipmentRequest").each {
  |node| puts node.text
}
Run Code Online (Sandbox Code Playgroud)

结果我得到:

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
Run Code Online (Sandbox Code Playgroud)

我还想打印标签名称并跳过带有空白值的标签/节点:

MemberID: MID-0000001

MemberName: Bruce

DeliveryId: 0000001

OrderNumber: ON-000000001

ShipToName: Alan

ShipToZip: 123-4567

ShipToStreet: West

etc...
Run Code Online (Sandbox Code Playgroud)

Mar*_*mas 9

你基本上想要所有的叶子元素.您可以在单个XPath表达式中捕获所有这些:

leaves = doc.xpath('//*[not(*)]')

leaves.each do |node|
  puts "#{node.name}: #{node.text}" unless node.text.empty?
end
Run Code Online (Sandbox Code Playgroud)

输出:

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
Run Code Online (Sandbox Code Playgroud)

XPath的解释

XPath //*[not(*)]查找所有叶元素.它是如何做到的?让我们分解一下:

  • //装置通过扫描整个文档.
  • *装置的任何元件,所以//*相匹配的文档中的所有元素.
  • 部分[]称为谓词,它约束前一个表达式.我读它就像"这样".它的范围是元素的子元素,因此例如a[b]意味着所有a元素都具有b子元素.
  • not()简单地是一个布尔否定,所以not(*)意思是"没有元素",所以在谓语,意思是"无子元素".

总而言之,您拥有"文档中的所有元素,使它们没有任何子元素"==叶元素.

另一个版本

在评论中,@ Phrogz做了一个很好的补充,通过添加另一个谓词来移动逻辑,检查元素是否为XPath表达式为空.这有两个好处:

  • 它会提高性能,因为它不会返回所有叶子,然后检查它们.这在大型文档中可能会很明显,或者有很多空叶.
  • 它变成一个单行!

puts doc.xpath('//*[not(*)][text()]').map{ |n| "#{n.name}: #{n.text}" }

含义"每个没有子元素的元素,但至少有一个子文本节点."

  • +1当XPath闪耀时,这是其中一个示例. (2认同)