【问题标题】:Delete namespace from xmlstarlet output从 xmlstarlet 输出中删除命名空间
【发布时间】:2017-03-25 01:17:18
【问题描述】:

背景

希望从以下 XML 内容中提取元素:

<ui:composition xmlns="http://www.w3.org/1999/xhtml"
                xmlns:h="http://java.sun.com/jsf/html"
                xmlns:f="http://java.sun.com/jsf/core"
                xmlns:ui="http://java.sun.com/jsf/facelets">
    <h:inputText id="id"/>
    ...
</ui:composition>

提取

所有h:inputText元素都可以使用:

xmlstarlet sel -t -c "//h:inputText" filename.xml

问题

这会产生以下命名空间感染的输出:

<h:inputText
    xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:ui="http://java.sun.com/jsf/facelets" id="id"/>

问题

如何从输出中抑制命名空间?

想法

使用正则表达式进行后期处理;但是:

  • sed 没有非贪婪匹配;
  • perl 太重(需要复杂的正则表达式)。

通过 xmllint 或 xmlstarlet 管道进行第二次传递,但这需要格式良好的 XML 文档。

使用 xmllint 会带来一系列命名空间问题。

生成仅包含 ui:compositionh:inputText 元素的文档:

<ui:composition
    xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:ui="http://java.sun.com/jsf/facelets">
  <h:inputText id="id"/>
  <h:inputText id="id"/>
</ui:composition>

这很棘手,因为h:inputText 元素可以出现在文档的任何深度。

【问题讨论】:

    标签: xml xpath namespaces xmlstarlet xmllint


    【解决方案1】:

    您可以使用 XSLT。如果您想按原样输出h:inputText,您将无法抑制将前缀h: 绑定到uri http://java.sun.com/jsf/html 的命名空间声明。

    XSLT 1.0

    创建input.xsl:

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
      xmlns:h="http://java.sun.com/jsf/html">
      <xsl:output omit-xml-declaration="yes"/>
      <xsl:strip-space elements="*"/>
    
      <xsl:template match="/">
        <xsl:apply-templates select="//h:inputText"/>
      </xsl:template>
    
      <xsl:template match="h:inputText">
        <xsl:copy>
          <xsl:copy-of select="@*"/>
        </xsl:copy>
      </xsl:template>
    
    </xsl:stylesheet>
    

    xmlstarlet 命令

    xmlstarlet tr input.xsl filename.xml
    

    输出

    <h:inputText xmlns:h="http://java.sun.com/jsf/html" id="id"/>
    

    你可以在没有命名空间的情况下输出inputText...

    XSLT 1.0

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
      xmlns:h="http://java.sun.com/jsf/html" exclude-result-prefixes="h">
      <xsl:output omit-xml-declaration="yes"/>
      <xsl:strip-space elements="*"/>
    
      <xsl:template match="/">
        <xsl:apply-templates select="//h:inputText"/>
      </xsl:template>
    
      <xsl:template match="h:inputText">
        <inputText>
          <xsl:copy-of select="@*"/>
        </inputText>
      </xsl:template>
    
    </xsl:stylesheet>
    

    输出

    使用上面相同的命令行:

    <inputText id="id"/>
    

    注意:您可能需要在&lt;/xsl:copy&gt;(或第二个示例中的&lt;/inputText&gt;)之后添加&lt;xsl:text&gt;&amp;#xA;&lt;/xsl:text&gt; 以显式添加换行符。否则 xmlstartlet 可能会在一行中输出所有元素。 (在xsl:output 上使用xmlstarlet 1.6.1 和indent="yes" 对我没有帮助。)

    JSF 输出

    由于涉及到 JSF,请考虑:

    <xsl:stylesheet version="1.0"
                    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
                    xmlns:h="http://java.sun.com/jsf/html"
                    xmlns:f="http://java.sun.com/jsf/core"
                    xmlns:c="http://java.sun.com/jsp/jstl/core"
                    xmlns:ui="http://java.sun.com/jsf/facelets"
                    xmlns:a4j="http://richfaces.org/a4j"
                    exclude-result-prefixes="h f c ui a4j">
        <xsl:output method="xml" omit-xml-declaration="yes" />
        <xsl:strip-space elements="*"/>
    
        <xsl:template match="/">
            <h:html>
                <xsl:apply-templates select="//h:inputText"/>
            </h:html>
            <xsl:text>&#xA;</xsl:text>
        </xsl:template>
    
        <xsl:template match="h:inputText">
            <xsl:text>&#xA;</xsl:text>
            <h:inputText>
                <xsl:copy-of select="@*"/>
            </h:inputText>
            <xsl:text>&#xA;</xsl:text>
        </xsl:template>
    </xsl:stylesheet>
    

    【讨论】:

      【解决方案2】:

      一个 XSLT 样式表解决方案已在一段时间前发布,但通过 我最近发生在 xmlstarlet 1.6.1 版的实验 产生所需输出的命令行同上 &lt;inputText id="id"/&gt;,

      xmlstarlet sel -N = -t -m '//h:inputText' -e '{local-name()}' -c '@*' -b -n file.xml
      

      -N = 似乎将一个空前缀绑定到 null 命名空间。

      如果您将&lt;f:inputText id="id"/&gt;&lt;ui:inputText id="id"/&gt; 添加到 输入文件并将上面命令中的-m子句更改为 -m '//f:inputText | //h:inputText | //ui:inputText' 它产生 每个匹配节点的期望输出。这将是一个环形交叉路口, 和冗长的,做exclude-result-prefixes="f h ui"的方法 命令行。

      不出所料,user's guide 没有提到-N 的这种用法,源代码的parseNSArr(…) 也没有提供任何线索。 也许这是设计使然——他们怎么可能没有注意到呢? - 也许不是: -N = 语法看起来有些可疑。但我肯定会 远离中列出的sed -e 's/ xmlns.*=".*"//g' 方法 user's guide.

      【讨论】:

        【解决方案3】:

        sed 没有非贪婪匹配

        这还是太贪心了吗?

        sed -e 's/ xmlns[^=]*="[^"]*"//g'
        

        【讨论】: