使用XslCompiledTransform进行空白剥离

时间:2012-08-31 15:40:43

标签: c# xslt whitespace xslcompiledtransform

我正在尝试将大型应用从XslTransform迁移到已编译的xsl文件和XslCompiledTransform

该应用使用Xsl创建HTML文件,转换数据(Xml)已传递到数据库返回的Xsl XmlDataDocument

我现在改变所有这一切(至少是暂时的):

C#

 public string ProcessCompiledXsl(XmlDataDocument xml)
 {
       StringBuilder stringControl = new StringBuilder();
       XslCompiledTransform xslTran = new XslCompiledTransform();

       xslTran.Load(
           System.Reflection.Assembly.Load("CompiledXsl").GetType(dllName)
       );

       xslTran.Transform(xml, this.Arguments, XmlWriter.Create(stringControl, othersettings), null);

       return stringControl.ToString();
 }

XSL(仅举例)

...
  <xsl:output method="html" indent="yes"/>
  <xsl:template match="/">
       <xsl:for-each select="//Object/Table">
              <a href="#">
                     some text
              </a>
       </xsl:for-each>
  </xsl:template>

问题

这样可行,但xsl正在剥离输出标记之间的空格:

<a href="#">
   some text
</a><a href="#">
   some text
</a><a href="#">
   some text
</a><a...etc

我试过了:

  • 使用xml:space="preserve",但我无法让它工作
  • 覆盖OutputSettings,但我没有得到任何好结果(也许我错过了一些东西)
  • 使用xsl:output method="xml",但有效,但会创建自闭标签和许多其他问题

所以我不知道该怎么做。也许我没有做正确的事情。任何帮助都非常感激。

谢谢!

修改

只是为了将来的引用,如果你想解决这个问题,让每个XSL保持不变,可以尝试this C# class我写的,名为CustomHtmlWriter

基本上我所做的是从XmlTextWriter延伸并修改编写每个标记的startend的方法。

在这种特殊情况下,您可以像这样使用它:

    StringBuilder sb = new StringBuilder();
    CustomHtmlWriter writer = new CustomHtmlWriter(sb);

    xslTran.Transform(nodeReader, this.Arguments, writer);

    return sb.ToString();

希望它有所帮助。

4 个答案:

答案 0 :(得分:4)

<强>予。解决方案1 ​​:

让我先来分析一下这个问题

鉴于此源XML文档(由于您未提供任何内容而发明):

<Object>
 <Table>

 </Table>

 <Table>

 </Table>

 <Table>

 </Table>

 <Table>

 </Table>
</Object>

此转化

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="html" indent="yes"/>

  <xsl:template match="/">
       <xsl:for-each select="//Object/Table">
              <a href="#">
                     some text
              </a>
       </xsl:for-each>
  </xsl:template>
<!--
 <xsl:template match="Table">
   <a href="#">
    Table here
   </a>
 </xsl:template>
 -->
</xsl:stylesheet>

完全重现问题 - 结果是:

<a href="#">
                     some text
              </a><a href="#">
                     some text
              </a><a href="#">
                     some text
              </a><a href="#">
                     some text
              </a>

现在,只需取消评论评论模板并注释掉第一个模板:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="html" indent="yes"/>
<!--
  <xsl:template match="/">
       <xsl:for-each select="//Object/Table">
              <a href="#">
                     some text
              </a>
       </xsl:for-each>
  </xsl:template>
 -->
 <xsl:template match="Table">
   <a href="#">
    Table here
   </a>
 </xsl:template>
</xsl:stylesheet>

结果包含所需的缩进

 <a href="#">
    Table here
   </a>

 <a href="#">
    Table here
   </a>

 <a href="#">
    Table here
   </a>

 <a href="#">
    Table here
   </a>

这是解决方案1 ​​


<强> II。解决方案2 :

此解决方案可以将对现有XSLT代码所需的修改降至最低:

这是一个两遍转换

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:ext="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="ext">
 <xsl:output method="html"/>

  <xsl:template match="/">
    <xsl:variable name="vrtfPass1">
       <xsl:for-each select="//Object/Table">
              <a href="#">
                     some text
              </a>
       </xsl:for-each>
    </xsl:variable>

    <xsl:apply-templates select=
        "ext:node-set($vrtfPass1)" mode="pass2"/>
  </xsl:template>

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

  <xsl:template mode="pass2" match="*[preceding-sibling::node()[1][self::*]]">
   <xsl:text>&#xA;</xsl:text>
   <xsl:copy-of select="."/>
  </xsl:template>
</xsl:stylesheet>

我们的想法是,我们甚至不触及现有代码,只捕获其输出并仅使用几行附加代码,我们将输出格式化为具有所需的最终外观。

在同一个XML文档上应用此转换时,会产生相同的想要结果:

<a href="#">
                     some text
              </a>
<a href="#">
                     some text
              </a>
<a href="#">
                     some text
              </a>
<a href="#">
                     some text
              </a>

最后,这里演示了如何引入这一微小变化,而不涉及任何现有的XSLT代码

让我们在c:\temp\delete\existing.xsl中使用此现有代码:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="html"/>

  <xsl:template match="/">
    <xsl:for-each select="//Object/Table">
      <a href="#">
        some text
      </a>
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>

如果我们运行此操作,我们会得到有问题的输出

现在,我们不是运行existing.xsl,而是运行此转换

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:ext="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="ext">
 <xsl:import href="file:///c:/temp/delete/existing.xsl"/>
 <xsl:output method="html"/>


  <xsl:template match="/">
    <xsl:variable name="vrtfPass1">
       <xsl:apply-imports/>
    </xsl:variable>

    <xsl:apply-templates select=
        "ext:node-set($vrtfPass1)" mode="pass2"/>
  </xsl:template>

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

  <xsl:template mode="pass2" match="*[preceding-sibling::node()[1][self::*]]">
   <xsl:text>&#xA;</xsl:text>
   <xsl:copy-of select="."/>
  </xsl:template>
</xsl:stylesheet>

结果是想要的结果,现有代码完全不受影响

<a href="#">
        some text
      </a>
<a href="#">
        some text
      </a>
<a href="#">
        some text
      </a>
<a href="#">
        some text
      </a>

<强>解释

  1. 我们使用 xsl:import 导入位于导入优先级层次结构顶层的任何现有代码(未由其他样式表导入)。

    < / LI>
  2. 我们捕获变量中现有转换的输出。它有臭名昭着的RTF( Result Tree Fragment )需要转换为常规树才能进一步处理。

  3. 在捕获转换输出时,关键时刻正在执行 xsl:apply-imports 。这样可以确保现有代码中的任何模板(甚至是我们覆盖的模板 - 例如匹配/的模板)将被选择执行,就像现有转换本身执行时一样。)

  4. 我们使用 msxsl:node-set() 扩展功能将RTF转换为常规树(XslCompiledTransform也支持 EXSLT node-set() 扩展功能)

  5. 我们对如此制作的常规树进行化妆品调整。

  6. 请注意

    这代表了一种在不触及现有代码的情况下对现有转换进行后期处理的通用算法

答案 1 :(得分:1)

我不记得我头脑中的XML / XSLT空间保存的细节,但是更有可能丢弃空白的一个实例是在没有非空白文本的元素之间(即仅空白文本)节点,如</a></xsl:for-each>之间的节点。您可以使用<xsl:text>元素阻止此操作。

例如,在

之后
          <a href="#">
                 some text
          </a>

          <xsl:text>&#10;</xsl:text>

即。字面行结束字符。

这符合您的要求吗?

答案 2 :(得分:1)

我认为问题是:

  <xsl:output method="html" indent="yes"/> 

如果我没记错的话html尝试只关心空格,这对HTML的显示方式很重要。

如果您尝试:

  <xsl:output method="xml" indent="yes"/> 

然后它应该创建你期望的缩进空格。

答案 3 :(得分:1)

样式表中的空白文本节点始终被忽略,除非它们包含在xsl:text中。如果要将空格输出到结果树,请使用xsl:text。

(也可以在样式表中使用xml:space =“preserve”,但它通常不可取,因为它有不必要的副作用。)