Обертывание и удаление CDATA вокруг XML

Это мой xml. Моя цель - обернуть данные в узле Value с помощью CDATA при экспорте, а затем импортировать их обратно в столбец типа XML с удаленным CDATA.

<Custom>
     <Table>Shape</Table>
     <Column>CustomScreen</Column>
     <Value>Data</Value>
<Custom>

Прямо сейчас я заменяю "Данные" внутри узла "Значение" на XML из таблицы, а затем полагаю, что я помещаю в него CData, где ShapeInfo - это тип XML, а CustomPanel - первый узел [ShapeInfo] XML.

SET @OutputXML= replace(@OutputXML, 'Data', CAST((SELECT [ShapeInfo]      
                         FROM [Shape] WHERE [Shape_ID] = @ShapeID) as VARCHAR(MAX))

SET @OutputXML= replace(@OutputXML, '<CustomPanel', '<![CDATA[<CustomPanel')

Однако результат выглядит примерно так, хотя я ожидал, что CDATA содержит только информацию:

<Value>&lt;CustomPanel VisibilityIndicator=""&gt;&lText="No" Checked="False" Height="20" Width="50"/&gt;&lt;/Cell&gt;&lt;/Row&gt;&lt;/Table&gt;&lt;/CustomPanel&gt;</Value>

Затем я делаю некоторые динамические SQL для обновления этого столбца

EXEC('UPDATE ['+ @tableName +  '] SET [' + @columnName + '] = ''' + @nodeValue + ''' WHERE Shape_ID = ''' + @ShapeID + '''')

Мне сказали, что я могу использовать следующее для удаления CDATA, но я не использовал его.

declare @x xml
set @x=N'<Value>&lt;CustomPanel....... all the current info ...=&quot;&quot;&gt;</Value>'

select @x.value('(/Value)[1]', 'nvarchar(max)')

select '<![CDATA[' + @x.value('(/Value)[1]', 'nvarchar(max)') + ']]'

После проверки столбца снова кажется, что он содержит правильную информацию. Однако я никогда не изменял его обратно на XML из VARCHAR и не удалял символы CDATA, хотя они, кажется, исчезли, когда я проверял столбец. Так чего мне здесь не хватает? Это правильный способ сделать это?

2 ответа

Решение

Если вам нужен полный контроль над генерацией XML, вы можете использовать FOR XML EXPLICIT:

DECLARE @xml xml = '<Custom>
     <Table>Shape</Table>
     <Column>CustomScreen</Column>
     <Value>Data</Value>
</Custom>';

WITH rawValues AS
(
    SELECT
        n.value('Table[1]', 'nvarchar(20)') [Table],
        n.value('Column[1]', 'nvarchar(20)') [Column],
        n.value('Value[1]', 'nvarchar(20)') [Value]
    FROM @xml.nodes('Custom') X(n)
)
SELECT 1 AS Tag,
       NULL AS Parent,
       [Table] AS [Custom!1!Table!ELEMENT],
       [Column] AS [Custom!1!Column!ELEMENT],
       [Value] AS [Custom!1!Value!CDATA]
FROM rawValues 
FOR XML EXPLICIT

Он генерирует:

<Custom>
  <Table>Shape</Table>
  <Column>CustomScreen</Column>
  <Value><![CDATA[Data]]></Value>
</Custom>

Если вам нужно обратное, замените исходный XML и используйте ELEMENT вместо CDATA,

Если вам действительно нужно CDATA раздел в вашем XML, есть только два варианта

  • конкатенация строк (очень плохо)
  • FOR XML EXPLICIT (в этом случае вы получили ответ от Павла)

Но вы должны принять во внимание, что CDATA раздел существует только для ленивого ввода. Нет абсолютно никакой разницы, заключено ли содержимое в CDATA раздел или правильно сбежал. Поэтому Microsoft решила даже не поддерживать CDATA синтаксис в современных методах XML. Это просто не нужно.

Посмотрите на эти примеры:

- Я начинаю со строки, содержащей тот же контент, и CDATA

DECLARE @s VARCHAR(500)=
'<root>
<a>Normal Text</a>
<a>Text with forbidden character &amp; &lt;&gt;</a>
<a><![CDATA[Text with forbidden character & <>]]></a>
</root>';

- Эта строка преобразуется в XML.

DECLARE @x XML=CAST(@s AS XML);

- Это выход, и вы можете видеть, что CDATA раздел закодирован нет CDATA больше CDATA всегда будет заменен на допустимую экранированную строку:

SELECT @x;

<root>
  <a>Normal Text</a>
  <a>Text with forbidden character &amp; &lt;&gt;</a>
  <a>Text with forbidden character &amp; &lt;&gt;</a>
</root>

- Задний план ясно показывает, что внутренне XML не имеет CDATA больше

SELECT CAST(@x AS VARCHAR(500));

<root>
   <a>Normal Text</a>
   <a>Text with forbidden character &amp; &lt;&gt;</a>
   <a>Text with forbidden character &amp; &lt;&gt;</a>
</root>

- Читая узлы один за другим, показывает правильное содержание в любом случае

SELECT a.value('.','varchar(max)')
FROM @x.nodes('/root/a') AS A(a)

Normal Text
Text with forbidden character & <>
Text with forbidden character & <>

Единственная причина использовать CDATA и настаивать на том, что это должно быть включено в текстовое представление XML (которое не является XML!), являются требованиями третьей стороны или устаревшими.

И имейте в виду: если вы используете конкатенацию строк, вы можете хранить XML с удобочитаемым CDATA только в строковом формате. Всякий раз, когда вы приведете это к XML CDATA будут опущены. С помощью FOR XML EXPLICIT допускает безопасное хранение, но очень неуклюже с более глубокими вложениями. Это может быть нормально с внешним интерфейсом, но вы должны дважды подумать об этом...

Две ссылки на похожие ответы (от меня:-)):

Другие вопросы по тегам