2012-05-25 2 views
2

В моем XML-документе есть произвольно вложенные разделы. Учитывая ссылку на конкретный раздел, мне нужно найти все TextNode s в этом разделе , не включая подразделы.Найти все узлы text-абстракции(), кроме подраздела

Например, если ссылка на #a1 узел ниже, мне нужно только найти «А1» и «А1» текстовые узлы:

<root> 
    <section id="a1"> 
    <b>A1 <c>A1</c></b> 
    <b>A1 <c>A1</c></b> 
    <section id="a1.1"> 
     <b>A1.1 <c>A1.1</c></b> 
    </section> 
    <section id="a1.2"> 
     <b>A1.2 <c>A1.2</c></b> 
     <section id="a1.2.1"> 
     <b>A1.2.1</b> 
     </section> 
     <b>A1.2 <c>A1.2</c></b> 
    </section> 
    </section> 
    <section id="a2"> 
    <b>A2 <c>A2</c></b> 
    </section> 
</root> 

В случае это не было очевидно, выше составленные данные. В частности, атрибуты id могут не существовать в реальном документе.

Лучшее, что я придумал сейчас, чтобы найти все текстовые узлы в пределах раздела, а затем использовать Ruby, чтобы вычесть из те, которые я не хочу:

def own_text(node) 
    node.xpath('.//text()') - node.xpath('.//section//text()') 
end 

doc = Nokogiri.XML(mydoc,&:noblanks) 
p own_text(doc.at("#a1")).length #=> 4 

Могу ли я ремесло одно выражение XPath 1.0, чтобы найти эти узлы напрямую? Что-то вроде:

.//text()[ancestor::section = self] # self being the original context node 

ответ

3

Использование (для секции с id атрибутом, имеющим строковое значение "a1"):

//section[@id='a1'] 
     //*[normalize-space(text()) and ancestor::section[1]/@id = 'a1']/text() 

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

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

<xsl:template match="/"> 
    <xsl:copy-of select= 
     "//section[@id='a1'] 
      //*[normalize-space(text()) and ancestor::section[1]/@id = 'a1'] 
    "/> 
</xsl:template> 
</xsl:stylesheet> 

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

<root> 
    <section id="a1"> 
     <b>A1 
      <c>A1</c> 
     </b> 
     <b>A1 
      <c>A1</c> 
     </b> 
     <section id="a1.1"> 
      <b>A1.1 
       <c>A1.1</c> 
      </b> 
     </section> 
     <section id="a1.2"> 
      <b>A1.2 
       <c>A1.2</c> 
      </b> 
      <section id="a1.2.1"> 
       <b>A1.2.1</b> 
      </section> 
      <b>A1.2 
       <c>A1.2</c> 
      </b> 
     </section> 
    </section> 
    <section id="a2"> 
     <b>A2 
      <c>A2</c> 
     </b> 
    </section> 
</root> 

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

<b>A1 
      <c>A1</c> 
</b> 
<c>A1</c> 
<b>A1 
      <c>A1</c> 
</b> 
<c>A1</c> 

UPDATE: в случае, section элементов могут иметь одинаковые id атрибуты (или нет id атрибутов вообще) не использовать:

 (//section)[1] 
      //*[normalize-space(text()) 
      and 
       count(ancestor::section) 
      = 
       count((//section)[1]/ancestor::section) +1]/text() 

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

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

    <xsl:template match="/"> 
     <xsl:copy-of select= 
      "(//section)[1] 
       //*[normalize-space(text()) 
       and 
        count(ancestor::section) 
       = 
        count((//section)[1]/ancestor::section) +1] 
     "/> 
    </xsl:template> 
</xsl:stylesheet> 

Преобразования результата (то же самая):

<b>A1 
      <c>A1</c> 
</b> 
<c>A1</c> 
<b>A1 
      <c>A1</c> 
</b> 
<c>A1</c> 

Это выбирает точно s ame хотел текстовые узлы.

+0

Можете ли вы сделать это, не полагаясь на атрибут id? Это был всего лишь демонстрационный документ, иллюстрирующий и четко обсуждающий этот вопрос. Представьте вложенные элементы '

' без каких-либо отличительных атрибутов. – Phrogz

+0

Да, см. Обновление этого ответа. –

+0

Nice; Я забыл об использовании 'count()', но даже после того, как вы начали его использовать, я не мог понять, как вы «храните» счет. Это все еще не будет работать непосредственно в Ruby/XPath (поскольку уникальный узел является '.' При запуске нового контекста), но это, похоже, отвечает на вопрос об общем XPath. – Phrogz

1

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

//text()[ancestor::section[1]/@id = 'a1'] 
+0

Это будет работать только в том случае, если каждый раздел имеет уникальный атрибут id. Это случается в моих примерах данных выше, но не в общем решении. +1, но не принимайте это. – Phrogz

+0

@Phrogz: Если это так, вам нужно указать это в тексте вопроса. Вы также должны указать, как конкретный «раздел» можно выбрать однозначно, потому что это необходимый префикс требуемого выражения XPath. См. Мой ответ для решения, которое не зависит от уникальности идентификаторов. –

+0

@Dimitre Любой раздел может быть однозначно выбран с помощью, например, '// section [27]' или (в действительности для моего случая) 'doc.xpath ('// section'). Each {| section | ... используйте эту ссылку для конкретного раздела в качестве привязки для нового выражения XPath ...} ' – Phrogz

Смежные вопросы