【问题标题】:Replace Paired Empty nodes in xml with Self Closing Nodes用自闭节点替换 xml 中的配对空节点
【发布时间】:2017-06-21 11:36:22
【问题描述】:

我有一些 xml,其中一部分看起来像这样:

<BasicInfo>
  <Foo>80</Foo>
  <Bar>
  </Bar>
</BasicInfo>

我想用单个自闭合节点替换所有空节点打开和关闭对(如上面的&lt;Bar&gt;),以提供如下内容:

<BasicInfo>
  <Foo>80</Foo>
  <Bar />
</BasicInfo>

我知道在xml中,两者是等价的,xslt会选择它想如何输出空节点等等,但是我有很多空节点并且两种解释的预期文件大小差异很大足以值得担心;我希望尽可能高效地存储 xml。

上面的 xml 是从 SQL Server 创建的,然后使用另一个 XSLT 进行转换,该 XSLT 删除了 SQL Server 2008 留下的所有“xsi:nil”值和 xmlns 声明:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" version="1.0">
  <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" omit-xml-declaration="yes" />
  <xsl:strip-space elements="*" />
  <xsl:template match="*">
    <xsl:element name="{local-name(.)}">
      <xsl:apply-templates select="@* | node()" />
    </xsl:element>
  </xsl:template>
  <xsl:template match="@*">
    <xsl:attribute name="{local-name(.)}">
      <xsl:value-of select="." />
    </xsl:attribute>
  </xsl:template>
  <xsl:template match="@*[local-name(.)='noNamespaceSchemaLocation']" />
  <xsl:template xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" match="@xsi:nil" />
</xsl:stylesheet>

正是这个样式表留下了开闭对 - 有没有办法强迫它留下自闭节点,或者让一个新的 xslt 自己产生这种效果?

遗憾的是,删除节点不是一种选择:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" version="1.0">
  <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" omit-xml-declaration="yes" />
  <xsl:strip-space elements="*" />
  <!--Remove all the empty nodes-->
  <!--copy nodes-->
  <xsl:template match="node()|@*">
    <xsl:copy>
      <xsl:apply-templates select="node()|@*" />
    </xsl:copy>
  </xsl:template>
  <!--match only those with no contents at all-->
  <xsl:template match="*[not(@*|*|comment()|processing-instruction()) and normalize-space()='']" />
  <!--now check all those that have existing but empty children and don't return the children if they are empty-->
  <xsl:template match="*">
    <xsl:copy>
      <xsl:if test="descendant::text()">
        <xsl:apply-templates select="node()" />
      </xsl:if>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

但这是我得到的最接近的。我发现其他人在问这个问题,但我对 xslt 的了解不足意味着我无法根据自己的需要更改解决方案(花了很长时间才解决上述问题!)。似乎遵循规范化空间的方法是要走的路?非常感谢您的帮助!

注意。 xml 由 SQL Server 2008 R2 使用FOR XML PATH 生成,XSLT 脚本存储在服务器上的 xml 列中; SSIS 2008 中操作数类型为 XSLT 的 XML 任务应用了转换。

【问题讨论】:

  • 此 XML 的创建是否在您的控制之下?
  • 是的,需要注意的是我使用 SQL Server 来制作它。
  • &lt;xsl:strip-space elements="*"/&gt; 应该这样做。您是否正在使用XSLCompiledTransform 执行转换?您可能需要将XSLCompiledTransform.OutputSettings 传递给XmlWriter.Create,以便XmlWriter 使用您在样式表中定义的输出设置。
  • 我最初对&lt;xsl:strip-space elements="*"/&gt; 也有同样的想法,但我认为我一定是错的。我不知道我是否在使用 XSLCompiledTransform...它只是 SSIS 中的一个 XML 任务,将一个变量作为我的 xml 文件,另一个作为 XSLT。我是不是应该改用脚本任务,照你说的做?
  • 您能否展示一个首先生成 XML 的查询?很有可能,它可以被修改,这样它就可以产生这些讨厌的节点了。

标签: sql-server xml sql-server-2008 xslt ssis-2008


【解决方案1】:

给定:

XML

<BasicInfo>
  <Foo>80</Foo>
  <Bar>
  </Bar>
</BasicInfo>

以下样式表:

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>

<!-- identity transform -->
<xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>

</xsl:stylesheet>

将返回:

<?xml version="1.0" encoding="UTF-8"?>
<BasicInfo>
  <Foo>80</Foo>
  <Bar/>
</BasicInfo>

说明:

&lt;xsl:strip-space elements="*"/&gt; 指令将删除 Bar 元素包含的纯空格文本节点,处理器将自动输出一个空元素作为自闭合标记。

【讨论】:

  • 我之前尝试过与此类似的脚本,当时我添加了&lt;xsl:strip-space elements="*"/&gt; 位,但它没有工作(请参阅问题中的 cmets,在此答案之后添加)。跨度>
  • 如果你得到不同的结果,那么要么你的输入不同,要么处理器的行为不同。处理器没有义务使用自闭合标签,尽管大多数都会这样做。如果你不这样做,就没有办法强制它 - 至少不是通过样式表。
  • 嗯。那是不幸的。如果我要使用不同的方法来应用 XSLT 转换,我是否可以选择处理器或其他东西来改变行为 - 例如在 SSIS 中使用脚本任务而不是内置的 XML 任务?在您看来,您认为值得尝试,还是可能只是一个死胡同?
  • 恐怕我不知道你在SSIS有什么选择。
【解决方案2】:

NULL 的正常行为是完全省略元素...

如果我做对了,您可以放置​​ ELEMENTS XSINIL 以强制引擎引入所有元素,即使在 NULL 的情况下也是如此。

一个 hacky 技巧可能是删除像这里这样的属性:

CREATE TABLE #Demo ( Id INT IDENTITY(1,1)   NOT NULL 
                    ,Value1 VARCHAR(30) NOT NULL 
                    ,Value2 VARCHAR(30) NULL ); 
INSERT #Demo(Value1,Value2) 
    VALUES('Bar',NULL); 


WITH XMLNAMESPACES ('http://tempuri.org/MySchema.xsd' AS xsd) 
SELECT This.Id 
     ,( SELECT T.Value1 
              ,T.Value2
        FROM    #Demo   T 
        WHERE   T.Id = This.Id 
        FOR XML PATH('BasicInfo'),ELEMENTS XSINIL,TYPE) AS TheXml
INTO #Demo2
FROM    #Demo   AS This;
UPDATE #Demo2 SET TheXml.modify('delete (//*/@*[local-name()="nil"])');

SELECT * FROM #Demo2 
GO
DROP TABLE #Demo
DROP TABLE #Demo2;

【讨论】:

  • 这肯定会在 SQL 中提供正确的输出 - 我明天会试一试。可惜没有封装在一个XSLT中,不过有hacky的trick总比没有trick的好!
  • 在 SQL Server 中使用 modify 确实提供了正确的解决方案,尽管如果我随后使用任何样式表,它们都会被恢复。我将使用它并尝试解决问题。似乎很遗憾,这种方式应该有不同的行为,而没有任何选择。
  • @HighPlainsGrifter 主要问题是:为什么这很重要?根据您的问题,您想节省存储空间...至少对于 SQL-Server,XML 不存储为您看到的字符串,而是存储为层次结构树。这里没有区别... 正常行为 是省略 NULL 值元素(甚至更少字节!)。只需添加一个模式来告诉消费者哪些元素是预期的?再说一遍:为什么?
  • SSIS 包保存的最终 .xml 文件的文件大小受 .xml 内容的影响,并且如 OP 中所述,删除节点似乎不是一种选择。这样做的原因与问题无关 - 如果我有空的话,我会这样做,并且可能需要花费更多时间来达到这一点。可悲的是,我发现自己处于一个我认为回答这个问题会很有用的位置。
  • @HighPlainsGrifter 回答这个问题会很有用:这个问题由 michael.hor257k 回答:没有办法强迫它 (除了 hacky 技巧)... XML 的内部 format 只要有效就应该无关紧要。在您的情况下,我假设有一个阅读工具或模式合同要求 NULL 值作为空节点(而不是缺少节点)。即使你强迫它(如上),下一个(XSLT-)步骤可能会破坏你的努力......
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-08-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-13
  • 1970-01-01
  • 2018-03-05
相关资源
最近更新 更多