如何在XSLT的XML输出中编写纯空白文本节点

时间:2018-10-29 00:22:57

标签: xslt xslt-1.0 xslcompiledtransform

我们有一条消息传递管道,其中包括XML到XML的转换。

对于这样的源文档(也可能在一行中而不设置格式):

<doc>
  <a>Foo</a>
  <b>Bar1</b>
  <b>Bar2</b>
  <b>Bar3</b>
  <c>Baz</c>
</doc>

我需要转换的XML输出为(注意换行符):

<x>Bar1
Bar2
Bar3</x>

但是我得到的输出是:

<x>Bar1Bar2Bar3</x>

样式表如下:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output omit-xml-declaration="yes" method="xml" version="1.0" />

  <xsl:template match="/">
    <x>
      <xsl:for-each select="//b">
        <xsl:value-of select="." />
        <xsl:if test="position() != last()">
          <xsl:text>&#xD;&#xA;</xsl:text>  <!-- something wrong here? -->
        </xsl:if>
      </xsl:for-each>
    </x>
  </xsl:template>
</xsl:stylesheet>

如果我在文本节点上添加一个非空格字符,那么我最终将保留换行符。因此,如果我将xsl:text节点修改为(请注意添加的连字符):

<xsl:text>-&#xD;&#xA;</xsl:text>

然后我得到输出:

<x>Bar1-
Bar2-
Bar3</x>

如何生成所需的输出?

请注意,我们仅限于XSLT 1.0。

更新

我还做了一些测试。下面是重现此问题的完整代码。有趣的是,此代码在.Net Framework 4.5和.Net Core 2.1下运行时会重现该问题,但在Mono下运行时会提供所需的输出。

using System;
using System.IO;
using System.Reflection;
using System.Text;
using System.Xml;
using System.Xml.Xsl;

namespace xslt
{
    class Program
    {
        static void Main(string[] args)
        {
            var doc = new XmlDocument();
            doc.LoadXml(@"<doc><a>Foo</a><b>Bar1</b><b>Bar2</b><b>Bar3</b><c>Baz</c></doc>");

            var xsl = new XmlDocument();
            xsl.LoadXml(@"<?xml version='1.0' encoding='utf-8'?>
<xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform' version='1.0'>
<xsl:output omit-xml-declaration='yes' method='xml' version='1.0' />

    <xsl:template match='/'>
        <x>
        <xsl:for-each select='//b'>
            <xsl:value-of select='.' />
            <xsl:if test='position() != last()'>
                <xsl:text>&#xD;&#xA;</xsl:text>  <!-- something wrong here? -->
            </xsl:if>
        </xsl:for-each>
        </x>
    </xsl:template>
</xsl:stylesheet>");

            var xslt = new XslCompiledTransform();
            xslt.Load(xsl);

            using (var stream = new MemoryStream())
            {
                xslt.Transform(doc, null, stream);
                Console.WriteLine(Encoding.UTF8.GetString(stream.ToArray()));
            }
        }
    }
}

2 个答案:

答案 0 :(得分:0)

  

如何在XSLT的XML输出中保留纯空白文本节点

如果您确实要保留text()元素之间的b节点,则可以将它们与XPath表达式匹配

text()[preceding::*[1][self::b]][following::*[1][self::b]]

,然后使用xsl:copy-of复制其全部内容。整个模板集可能看起来像这样:

<xsl:template match="/doc">
    <x>
      <xsl:apply-templates select="node()|@*" />
    </x>
</xsl:template>

<xsl:template match="b">
      <xsl:value-of select="." />
</xsl:template>  

<xsl:template match="text()" />

<xsl:template match="text()[preceding::*[1][self::b]][following::*[1][self::b]]">
    <xsl:copy-of select="." />
</xsl:template>

这还将复制之间的空格,而不仅仅是换行符,因此输出看起来像

<x>Bar1-
  Bar2
  Bar3</x>

答案 1 :(得分:0)

我可以通过在样式表中添加脚本块来构建换行符分隔的值来实现此功能。

我仍然想知道纯XSL是否有可能。

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
                xmlns:msxsl="urn:schemas-microsoft-com:xslt"
                xmlns:userCSharp="http://schemas.microsoft.com/BizTalk/2003/userCSharp">
  <xsl:output omit-xml-declaration="yes" method="xml" version="1.0" />

  <xsl:template match="/">
    <x>
      <xsl:value-of select='userCSharp:JoinLines(//b)' />
    </x>
  </xsl:template>

  <msxsl:script language="C#" implements-prefix="userCSharp">
    <![CDATA[

public string JoinLines(XPathNodeIterator nodes)
{
  var builder = new StringBuilder();
  while (nodes.MoveNext())
  {
    builder.AppendLine(nodes.Current.Value);
  }
  return builder.ToString().Trim();
}

    ]]>
  </msxsl:script>
</xsl:stylesheet>