I learned from 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;
I am uncertain how elegant this is, as text
column is of type ntext
, and the calls to sys.sp_xml_preparedocument
and sys.sp_xml_removedocument
mean, that you need to pay a bit more attention, when you include this into other queries. Probably the worst draw-back is, that you need to do this RBAR.
Still this is a solution without casting xml
to varchar
, so it should be difficult to spoof.
Listing all namespaces:
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;