2016-10-10 11 views
1

У меня есть следующий входной XML-файл:выражения XPath для получения узла на основе значения атрибута

<rootnode> 
<section id="1" status="fail"> 
    <outer status="fail"> 
    <inner status="fail"/> 
    <inner status="pass"/> 
    </outer> 
    <outer status="pass"> 
    <inner status="pass"/> 
    </outer> 
    <outer status="pass"/> 
    <outer status="fail"/> 
</section> 
<section id="2" status="fail"> 
    <outer status="fail"> 
    <inner status="pass"/> 
    <inner status="fail"/> 
    <inner status="inc"/> 
    </outer> 
</section> 
</rootnode> 

Я хочу, чтобы отфильтровать все не обанкротиться узлами состояния, так что результат выглядит следующим образом:

<rootnode> 
<section id="1" status="fail"> 
    <outer status="fail"> 
    <inner status="fail"/> 
    </outer> 
    <outer status="fail"/> 
</section> 
<section id="2" status="fail"> 
    <outer status="fail"> 
    <inner status="fail"/> 
    </outer> 
</section> 
</rootnode> 

<rootnode> не обязательно должен быть включен в результат. Я попытался использовать xmllint с выражением xpath. Я могу извлечь определенные узлы с

xmllint --xpath "//inner" input.xml 
xmllint --xpath "//@status" input.xml 

но только либо вернуть узлы без учета стоимости status или возвращать только атрибут без окружающих узлов.

Есть ли способ сделать это с помощью выражения xpath? Если нет, то простое решение, которое включает в себя другие инструменты bash, тоже.

+2

Вам нужен не XPATH XSLT, вы знаете, что, когда XPath возвращает узел со статусом = «сбой», который имеет внутренний элемент, как внутренний статус = «пройди», ты тоже получишь это. – SomeDude

ответ

2

Как @svasa в комментарии, вы должны использовать XSLT. Вы можете легко обрабатывать XSLT в Баш с xsltproc, xmlstarlet (используя tr команду), Saxon (Java на command line) и т.д.

Вот пример использования xsltproc:

$ xsltproc so.xsl so.xml 
<?xml version="1.0"?> 
<rootnode> 
    <section id="1" status="fail"> 
    <outer status="fail"> 
     <inner status="fail"/> 
    </outer> 
    <outer status="fail"/> 
    </section> 
    <section id="2" status="fail"> 
    <outer status="fail"> 
     <inner status="fail"/> 
    </outer> 
    </section> 
</rootnode> 

XML Input (so.xml)

<rootnode> 
    <section id="1" status="fail"> 
     <outer status="fail"> 
      <inner status="fail"/> 
      <inner status="pass"/> 
     </outer> 
     <outer status="pass"> 
      <inner status="pass"/> 
     </outer> 
     <outer status="pass"/> 
     <outer status="fail"/> 
    </section> 
    <section id="2" status="fail"> 
     <outer status="fail"> 
      <inner status="pass"/> 
      <inner status="fail"/> 
      <inner status="inc"/> 
     </outer> 
    </section> 
</rootnode> 

XSLT 1.0 (so.xsl)

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

    <xsl:template match="@*|node()"> 
    <xsl:copy> 
     <xsl:apply-templates select="@*|node()"/> 
    </xsl:copy> 
    </xsl:template> 

    <xsl:template match="*[@status[not(normalize-space()='fail')]]"/> 

</xsl:stylesheet> 

У меня есть небольшой дополнительный вопрос, если вы не возражаете. Когда файл input.xml не содержит каких-либо состояний = сбои узлов, то вывод - это всего две строки: <?xml version="1.0"?> и <rootnode/>. Это Возможны два варианта подавления вывода в этом случае? Это не действительно проблема, я знаю, как обойти это в bash. Я просто интересуюсь, если есть чистое решение через xslt.

Что вы можете сделать, это опустить объявление XML (omit-xml-declaration="yes" в xsl:output) и проверьте, есть ли какие-либо элементы с status="fail". Я хотел бы использовать ключ (xsl:key) для этого ...

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 
    <xsl:output indent="yes" omit-xml-declaration="yes"> 
    <!--If you need to output the declaration when there 
    are elements with status="fail", it might be best to post process files that 
    only contain the xml declaration.--> 
    </xsl:output> 
    <xsl:strip-space elements="*"/> 

    <!--Key of all elements with status="fail".--> 
    <xsl:key name="fails" match="*[@status='fail']" use="@status"/> 

    <xsl:template match="/*[not(key('fails','fail'))]"> 
    <!--If there aren't any elements with status="fail", don't process 
    anything else.--> 
    </xsl:template> 

    <xsl:template match="@*|node()"> 
    <xsl:copy> 
     <xsl:apply-templates select="@*|node()"/> 
    </xsl:copy> 
    </xsl:template> 

    <xsl:template match="*[@status[not(normalize-space()='fail')]]"/> 

</xsl:stylesheet> 
+0

У меня есть небольшой вопрос, если вы не возражаете. Когда файл input.xml не содержит узлов 'status = fail', то вывод состоит всего из двух строк:' 'И' '. Возможно ли, что в этом случае можно полностью исключить вывод? На самом деле это не проблема, я знаю, как обойти это в bash. Меня просто интересует, есть ли чистое решение через xslt. – nautical

+1

@nautical - см. Мое редактирование для одного варианта. –

+1

Спасибо. Очень признателен. – nautical