使用 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 > 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() > 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 > 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 > 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() > 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 > 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() > 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 > 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 转换中动态更改名称空间以进行进一步转换