2012-01-17 11 views
4

Я разбираю некоторые XML, используя Nokogiri и XPath. Когда я делаю это:XPath для выбора только дочерних элементов (не пустые текстовые узлы)

doc.xpath('//Order/child::node()').each do |node| 
    puts node.name 
end 

Он печатает все узлы, но и между именами, он печатает «текст». Я думаю, я знаю, почему:

В моей XML, есть пробелы между узлами, как это: "<a1>hi</a1> \n <a2>bye</a2>"

Есть ли способ, что я могу сказать ему, чтобы игнорировать вещи между узлами?

+2

Есть ли у вас какие-либо неэлементные (текстовые) дети '', которые вы хотите сохранить? Любые узлы комментариев? (В будущем предоставление фактического тестового образца XML и выход, который вы хотите, помогут вам получить лучшие результаты.) – Phrogz

+0

Текст (включая пробелы) между * элементами * также являются * узлами *. То, что вы хотите найти, - это просто элементы. –

ответ

7

Использование:

//Order/node()[not(self::text()[not(normalize-space())])] 

это выбирает все дочерние узлы любого Order элемента, за исключением тех, которые являются текстовыми узлами, состоящими полностью из белого пространства.

XSLT - на основе проверки:

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 
<xsl:output omit-xml-declaration="yes" indent="yes"/> 


<xsl:template match="/*"> 
    <xsl:variable name="vSel1" select="//Order/node()"/> 
    <xsl:variable name="vSel2" select= 
    "//Order/node()[not(self::text()[not(normalize-space())])]"/> 

    <xsl:for-each select="$vSel1"> 
     <xsl:value-of select="concat('&#xA;',position(), ': ')"/> 
     <xsl:copy-of select="."/> 
     <xsl:text>&#xA;</xsl:text> 
    </xsl:for-each> 
================ 
    <xsl:for-each select="$vSel2"> 
     <xsl:value-of select="concat('&#xA;',position(), ': ')"/> 
     <xsl:copy-of select="."/> 
     <xsl:text>&#xA;</xsl:text> 
    </xsl:for-each> 
</xsl:template> 
</xsl:stylesheet> 

когда это преобразование применяется на следующий документ XML:

<t> 
<Order> 
    <a/> 
    <b>xxx</b> 
    <c/> 
</Order> 
<Order> 
    <d/> 
    <e>xxx</e> 
    <f/> 
</Order> 
</t> 

два XPath выражения вычисляются и узлы из двух выводятся соответствующие наборы выбранных узлов, каждому из которых предшествует его номер позиции:

1: 


2: <a/> 

3: 


4: <b>xxx</b> 

5: 


6: <c/> 

7: 


8: 


9: <d/> 

10: 


11: <e>xxx</e> 

12: 


13: <f/> 

14: 


================ 

1: <a/> 

2: <b>xxx</b> 

3: <c/> 

4: <d/> 

5: <e>xxx</e> 

6: <f/> 
4

Если вы хотите только элементы, использовать лучший XPath: просить /* подберет вам все ребенок элементы:

require 'nokogiri' 
doc = Nokogiri.XML("<r><a>1</a>\n\t<b>2</b></r>") 
p doc.xpath('/r/child::node()').map(&:name) 
#=> ["a", "text", "b"] 

p doc.xpath('/r/*').map(&:name) 
#=> ["a", "b"] 

В качестве альтернативы, вы можете попросить Nokogiri выбросить любые текстовые заметки, которые только пробельные:

doc2 = Nokogiri.XML("<r><a>1</a>\n\t<b>2</b></r>",&:noblanks) 
p doc2.xpath('/r/child::node()').map(&:name) 
#=> ["a", "b"] 

Или, вы можете использовать рубин для фильтрации NodeSet дополнительно на основе произвольных критериев:

mine = doc.xpath('/r/child::node()').select do |node| 
    node.type != Nokogiri::XML::Node::TEXT_NODE || node.content =~ /\S/ 
end 
p mine.map(&:name) 
#=> ["a", "b"]