使用 XSLT 创建 PLSQL 语句以输出 XML

Posted

技术标签:

【中文标题】使用 XSLT 创建 PLSQL 语句以输出 XML【英文标题】:Using XSLT to create a PLSQL statement to output XML 【发布时间】:2015-06-16 09:53:41 【问题描述】:

我已经在互联网上搜索了一种工具来执行此操作,但我没有找到一个,所以我认为自己创建应该很简单。 我想创建一个 XSLT,在其中输入任意 xml 文件,它将输出一个 select 语句,我可以在 Oracle 数据库中使用该语句来生成输入 xml。

例如 如果我给它:

<?xml version="1.0"?>
<test xmlns="dddd" xmlns:xxx="ddd222" someatt="val">
    <xxx:f>E</xxx:f>
    <g>G</g>
    <h xmlns="anotherns">H</h>
    <zz:i xmlns:zz="yetanotherns">I</zz:i>
</test>

我想要以下输出:

select 
    xmlelement("test"
    ,xmlattributes(
      'dddd' as "xmlns"
      ,'ddd222' as "xmlns:xxx"
      ,'val' as "someatt"
    )
        ,xmlelement("xxx:f",'E')
        ,xmlelement("g",'G')
        ,xmlelement("h"
      ,xmlattributes('anotherns' as "xmlns")
      ,'H'
    )
        ,xmlelement("zz:i"
      ,xmlattributes('yetanotherns' as "xmlns:zz")
      ,'I'
    )
  )
from dual;

我几乎一直到那里。我可以使用我当前的 XSLT 进行以下输出:

select 
    xmlelement("test"
        ,xmlattributes(
            'val' as "someatt"
        )
        ,xmlelement("xxx:f",'E')
        ,xmlelement("g",'G')
        ,xmlelement("h",'H')
        ,xmlelement("zz:i",'I')
        )
from dual;

这是完美的,只是它缺少 xmlns 属性。问题是输入文档中的 xmlns 和 xmlns:*** 属性没有被视为普通属性,并且在运行 xslt 时似乎不可见。 有没有办法让他们留下来?

我的xslt如下:

<?xml version="1.0" encoding="UTF-8"?>

<xsl:stylesheet 
    version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common"

>
<xsl:output method="text" indent="no" omit-xml-declaration="yes" />

<xsl:template match="/">


    <xsl:text>select 
    </xsl:text>
    <xsl:apply-templates/>
    <xsl:text>
from dual;</xsl:text>
</xsl:template>


<xsl:template match="node()">
    <xsl:text>xmlelement("</xsl:text>
    <xsl:value-of select='name()'/>
    <xsl:text>"</xsl:text>

    <!--Lots of tabs for indenting-->
<xsl:variable name='tabs' xml:space="preserve">                                                                                                                                                                                                                                                                                                                                                                                             </xsl:variable>

    <xsl:variable name='nl'><xsl:text>
    </xsl:text><xsl:value-of select='substring($tabs,0,count(ancestor::*)+2)'/></xsl:variable>

    <xsl:variable name='att_children' select='count(@*)'/>
    <xsl:if test="$att_children &gt; 0">
        <xsl:value-of select="$nl"/>
        <xsl:text>,xmlattributes(</xsl:text>
        <xsl:for-each select='./@*'>
            <xsl:value-of select="$nl"/><xsl:text>  </xsl:text>
            <xsl:if test="position() &gt; 1"><xsl:text>,</xsl:text></xsl:if>
            <xsl:text>'</xsl:text>
            <xsl:value-of select="."/>
            <xsl:text>' as "</xsl:text>
            <xsl:value-of select="name()"/>
            <xsl:text>"</xsl:text>
        </xsl:for-each>
        <xsl:value-of select="$nl"/>
        <xsl:text>)</xsl:text>
    </xsl:if>


    <xsl:variable name='children' select='count(*)'/>
    <!--<xsl:value-of select='$children'/>-->
    <xsl:choose>
        <xsl:when test='$children=0'>
            <xsl:text>,</xsl:text>
            <xsl:text>'</xsl:text>
            <xsl:value-of select='text()'/>
            <xsl:text>'</xsl:text>
        </xsl:when>
        <xsl:otherwise>
            <xsl:for-each select='./*'>
                <xsl:value-of select="$nl"/>
                <xsl:text>,</xsl:text>
                <xsl:apply-templates select='.'/>
            </xsl:for-each>
        </xsl:otherwise>
    </xsl:choose>
    <xsl:if test='$children &gt; 1'>
        <xsl:value-of select="$nl"/>
    </xsl:if>
    <xsl:text>)</xsl:text>
</xsl:template>

<xsl:template match="text()|@*"> </xsl:template> 

</xsl:stylesheet> 

【问题讨论】:

xmlns 不是属性,它是命名空间声明。您仍然可以在 XSLT 中访问它们,但您可能需要了解如何在 PLSQL/Oracle 中处理 XML 命名空间,以便知道首先生成什么输出,因为它可能与您当前显示的预期输出不同。跨度> @TimC 似乎(奇怪地)定义属性或命名空间是相同的功能,参见例如***.com/questions/437670/… 嗨,是的,我知道我想从 xslt 得到什么输出,这是问题所在。 PLSQL 将 xmlns 视为普通属性,我已经测试了问题中的语句并且它有效。 我的问题是如何检测 xslt 输入中的 xmlns 命名空间声明? 【参考方案1】:

我对你的问题只有部分解决方案。

它包括首先计算要输出的命名空间,以及属性:

<xsl:variable name='att_children' select='count(@* | namespace::*[not(name() = "xml")])'/>

然后在你的节点上定义的命名空间上循环(有一个额外的谓词来避免默认的 xml 命名空间):

<xsl:if test="$att_children &gt; 0">
    <xsl:value-of select="$nl"/>
    <xsl:text>,xmlattributes(</xsl:text>
    <xsl:for-each select='./@* | namespace::*[not(name() = "xml")]'>
        <xsl:value-of select="$nl"/><xsl:text>  </xsl:text>
        <xsl:if test="position() &gt; 1"><xsl:text>,</xsl:text></xsl:if>
        <xsl:text>'</xsl:text>
        <xsl:value-of select="."/>
        <xsl:text>' as "</xsl:text>
        <xsl:choose>
          <!-- for real attributes -->
          <xsl:when test="self::attribute">
              <xsl:value-of select="name()"/>
          </xsl:when>
          <!-- for namespaces -->
          <xsl:otherwise>
            <xsl:choose>
              <xsl:when test='name() = ""'>xmlns</xsl:when>
              <xsl:otherwise>
                <xsl:text>xmlns:</xsl:text>
                <xsl:value-of select="name()"/>
              </xsl:otherwise>
            </xsl:choose>
          </xsl:otherwise>
        </xsl:choose>
        <xsl:text>"</xsl:text>
    </xsl:for-each>
    <xsl:value-of select="$nl"/>
    <xsl:text>)</xsl:text>
</xsl:if>

这是我目前得到的:

select 
    xmlelement("test"
     ,xmlattributes(
       'dddd' as "xmlns"
       ,'ddd222' as "xmlns:xxx"
       ,'val' as "xmlns:someatt"
     )
     ,xmlelement("xxx:f"
      ,xmlattributes(
        'dddd' as "xmlns"
        ,'ddd222' as "xmlns:xxx"
      ),'E')
     ,xmlelement("g"
      ,xmlattributes(
        'dddd' as "xmlns"
        ,'ddd222' as "xmlns:xxx"
      ),'G')
     ,xmlelement("h"
      ,xmlattributes(
        'anotherns' as "xmlns"
        ,'ddd222' as "xmlns:xxx"
      ),'H')
     ,xmlelement("zz:i"
      ,xmlattributes(
        'dddd' as "xmlns"
        ,'ddd222' as "xmlns:xxx"
        ,'yetanotherns' as "xmlns:zz"
      ),'I')
     )
from dual;

限制是继承的命名空间在每个元素上重复。它有些多余,但通常不会影响有效的 XML。

很遗憾,我无法找到一种方法来检查命名空间是否已在祖先元素上定义,以避免这种重复。

【讨论】:

是的,感谢您查看此内容。重新计算命名空间是一种选择,但它变得非常复杂。我想使用与输入文档相同的名称空间前缀,但有可能在树的不同级别使用相同的前缀。同样的命名空间也有可能使用不同的前缀。我考虑将文档中的所有命名空间收集在一起并在顶层输出它们,但它可能会破坏事情。按照在输入文档中完全复制声明的规则应该更简单。【参考方案2】:

我不确定你是否能得到你想要的东西。 namespace:: 轴将返回对节点有效的所有命名空间,不一定是在声明该命名空间时(可能是祖先节点)

您可以做的是使用以下表达式,尝试检查名称空间前缀和 uri 第一次出现在 XML 中的时间

 <xsl:for-each select="namespace::*[name() != 'xml']">
      <xsl:if test="not(../ancestor::*/namespace::*[. = current()/. and name() = current()/name()])">

试试这个 XSLT(它可能需要在输出格式上做一些工作)

<xsl:stylesheet 
    version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common"

>
<xsl:output method="text" indent="no" omit-xml-declaration="yes" />

<xsl:template match="/">


    <xsl:text>select 
    </xsl:text>
    <xsl:apply-templates/>
    <xsl:text>
from dual;</xsl:text>
</xsl:template>


<xsl:template match="node()">
    <xsl:text>xmlelement("</xsl:text>
    <xsl:value-of select='name()'/>
    <xsl:text>"</xsl:text>

    <!--Lots of tabs for indenting-->
<xsl:variable name='tabs' xml:space="preserve">                                                                                                                                                                                                                                                                                                                                                                                             </xsl:variable>

    <xsl:variable name='nl'><xsl:text>
    </xsl:text><xsl:value-of select='substring($tabs,0,count(ancestor::*)+2)'/></xsl:variable>

    <xsl:variable name="namespaces">
        <xsl:if test="self::*">
                <xsl:for-each select="namespace::*[name() != 'xml']">
                    <xsl:if test="not(../ancestor::*/namespace::*[. = current()/. and name() = current()/name()])">
                        <xsl:text>,'</xsl:text>
                        <xsl:value-of select="." />
                        <xsl:text>' as "xmlns</xsl:text>
                        <xsl:if test="name() != ''">
                            <xsl:text>:</xsl:text>
                            <xsl:value-of select="name()" />
                        </xsl:if>
                        <xsl:text>"</xsl:text>
                    </xsl:if>
                </xsl:for-each>
        </xsl:if>
    </xsl:variable>

    <xsl:variable name='att_children' select='count(@*)'/>
    <xsl:if test="$att_children &gt; 0 or $namespaces != ''">
        <xsl:value-of select="$nl"/>
        <xsl:text>,xmlattributes(</xsl:text>
        <xsl:value-of select="substring($namespaces, 2)" />
        <xsl:for-each select='./@*'>
            <xsl:value-of select="$nl"/><xsl:text>  </xsl:text>
            <xsl:if test="position() &gt; 1 or $namespaces != ''"><xsl:text>,</xsl:text></xsl:if>
            <xsl:text>'</xsl:text>
            <xsl:value-of select="."/>
            <xsl:text>' as "</xsl:text>
            <xsl:value-of select="name()"/>
            <xsl:text>"</xsl:text>
        </xsl:for-each>
        <xsl:value-of select="$nl"/>
        <xsl:text>)</xsl:text>
    </xsl:if>


    <xsl:variable name='children' select='count(*)'/>
    <!--<xsl:value-of select='$children'/>-->
    <xsl:choose>
        <xsl:when test='$children=0'>
            <xsl:text>,</xsl:text>
            <xsl:text>'</xsl:text>
            <xsl:value-of select='text()'/>
            <xsl:text>'</xsl:text>
        </xsl:when>
        <xsl:otherwise>
            <xsl:for-each select='./*'>
                <xsl:value-of select="$nl"/>
                <xsl:text>,</xsl:text>
                <xsl:apply-templates select='.'/>
            </xsl:for-each>
        </xsl:otherwise>
    </xsl:choose>
    <xsl:if test='$children &gt; 1'>
        <xsl:value-of select="$nl"/>
    </xsl:if>
    <xsl:text>)</xsl:text>
</xsl:template>

<xsl:template match="text()|@*"> </xsl:template> 

</xsl:stylesheet> 

这可能不适用于您将命名空间前缀重新定义为不同的 URI,然后将其重新定义回原始 URI。

【讨论】:

以上是关于使用 XSLT 创建 PLSQL 语句以输出 XML的主要内容,如果未能解决你的问题,请参考以下文章

xslt 不会选择在 XSLT 转换中动态更改名称空间以进行进一步转换

删除姓氏,但使用 XSLT 保持首字母

转换 PLSQL 函数以处理利用 Oracle ARRAY 的 jdbc 语句

plsql 中如何使用dbms_output输出结果

plsql 中如何使用dbms_output输出结果

在plsql中输出SQL语句