试图列出已声明的 XML-namespaces

Trying to list declared XML-namespaces

我们有许多应用程序通过提供 XML 数据来访问我们的 API。在某个时候,我们决定对传入的 NULL 值使用 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true",以便能够以统一的方式区分空值和 NULL 值。

因为向 xsi:nil 的过渡仍在进行中,我希望能够判断 xsi-命名空间是否被声明为调用应用程序是否会使用的指示器xsi:nil="true" 对应 NULL 个值。

我试过了

DECLARE @SomeXML xml = N'<ROOT xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><blubb><blah xsi:nil="true"/></blubb></ROOT>';

SELECT  @SomeXML.exist('declare namespace xsi="http://www.w3.org/2001/XMLSchema-instance"; //xsi:*, //@xsi:*');

但这只有在 XML 文档中实际引用了命名空间时才有效。像 //@xmlns:* 这样的查询会导致错误

Msg 2229, Level 16, State 1, Line 6
XQuery [query()]: The name "xmlns" does not denote a namespace.

而对 //@*:xsi 的查询只是 returns 什么都没有。

有什么方法可以确定 SQL Server 2016 中声明的 XML-命名空间?

我不确定是否有比转换为 nvarchar 并在 URI 上执行 CHARINDEX 更好的方法。我认为你不太可能得到误报,但感觉不对。

DECLARE @SomeXML xml = N'<ROOT xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><blubb><blah xsi:nil="true"/></blubb></ROOT>'

select CHARINDEX(N'"http://www.w3.org/2001/XMLSchema-instance"',CONVERT(nvarchar(max), @SomeXML))

(我们只在 URI 上搜索,这样即使在文档中使用了不同的前缀我们也能找到它)

问题是大多数 XML 工具都假定每个 "context"(例如 XML 文档、XPath 表达式等)都引入了与其相关的名称空间,因此不需要一种机制来探索其他 "contexts".

中的命名空间声明

我从this thread, that there is a rather old way to enumerate namespaces utilizing OPENXML那里学到了:

DECLARE @SomeXML xml = N'<ROOT xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.w3.org/2001/XMLSchema-instance"><blubb xmlns:xsiu="http://www.w3.org/2001/XMLSchema-instance"><blah xsi:nil="true"/></blubb></ROOT>';

DECLARE @hDoc int;

EXEC sys.sp_xml_preparedocument
    @hDoc OUTPUT,
    @SomeXML;

IF EXISTS ( SELECT  namespace = NULLIF(XmlnsAttribute.localname, 'xmlns'),
                    namespace_uri = XmlnsValue.text
            FROM    OPENXML( @hDoc, '//*' ) XmlnsAttribute
            INNER JOIN OPENXML( @hDoc, '//*' ) XmlnsValue ON XmlnsValue.parentid = XmlnsAttribute.id
            WHERE   XmlnsAttribute.prefix = 'xmlns'
                    AND XmlnsValue.nodetype = 3 /*text*/
                    AND CAST(XmlnsValue.text AS nvarchar(MAX)) = N'http://www.w3.org/2001/XMLSchema-instance' )
    PRINT 'Has http://www.w3.org/2001/XMLSchema-instance namespace';
ELSE
    PRINT 'Does not have http://www.w3.org/2001/XMLSchema-instance namespace';

EXEC sys.sp_xml_removedocument
    @hDoc;

我不确定这有多优雅,因为 text 列是 ntext 类型,并且对 sys.sp_xml_preparedocumentsys.sp_xml_removedocument 的调用意味着,您需要当您将其包含在其他查询中时,请多加注意。可能最糟糕的缺点是,您需要执行此 RBAR。

这仍然是一个没有将xml转换为varchar的解决方案,所以应该很难被欺骗。

列出所有命名空间:

DECLARE @SomeXML xml = N'<ROOT xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.w3.org/2001/XMLSchema-instance"><blubb xmlns:xsiu="http://www.w3.org/2001/XMLSchema-instance"><blah xsi:nil="true"/></blubb></ROOT>';

DECLARE @hDoc int;

EXEC sys.sp_xml_preparedocument
    @hDoc OUTPUT,
    @SomeXML;

-- All registered namespaces
WITH    XmlNodes
          AS (SELECT    OX.id,
                        OX.parentid,
                        OX.nodetype,
                        OX.localname,
                        OX.prefix,
                        OX.text
              FROM      OPENXML( @hDoc, '//*' ) OX)
    SELECT  namespace = NULLIF(XmlnsAttribute.localname, 'xmlns'),
            namespace_uri = XmlnsValue.text
    FROM    XmlNodes XmlnsAttribute
    INNER JOIN XmlNodes XmlnsValue ON XmlnsValue.parentid = XmlnsAttribute.id
    WHERE   XmlnsAttribute.prefix = 'xmlns'
            AND XmlnsValue.nodetype = 3
 /*text*/;

-- All registered namespaces with scope
WITH    XmlNodes
          AS (SELECT    OX.id,
                        OX.parentid,
                        OX.nodetype,
                        OX.localname,
                        OX.prefix,
                        OX.text
              FROM      OPENXML( @hDoc, '//*' ) OX),
        XmlNodesWithPath
          AS (SELECT    XN.id,
                        path = CAST(N'/' + ISNULL(XN.prefix + N':', N'') + XN.localname AS nvarchar(MAX))
              FROM      XmlNodes XN
              WHERE     XN.parentid IS NULL
              UNION ALL
              SELECT    XN.id,
                        path = XNWP.path + N'/' + ISNULL(XN.prefix + N':', N'') + XN.localname
              FROM      XmlNodesWithPath XNWP
              INNER JOIN XmlNodes XN ON XN.parentid = XNWP.id
                                        AND XN.nodetype = 1)
    SELECT  scope = Scope.path,
            namespace = NULLIF(XmlnsAttribute.localname, 'xmlns'),
            namespace_uri = XmlnsValue.text
    FROM    XmlNodesWithPath Scope
    INNER JOIN XmlNodes XmlnsAttribute ON XmlnsAttribute.parentid = Scope.id
    INNER JOIN XmlNodes XmlnsValue ON XmlnsValue.parentid = XmlnsAttribute.id
    WHERE   XmlnsAttribute.prefix = 'xmlns'
            AND XmlnsValue.nodetype = 3
 /*text*/;

EXEC sys.sp_xml_removedocument
    @hDoc;