代码之家  ›  专栏  ›  技术社区  ›  Andrew Cooper

如何在XSLT的XML输出中编写一个只有空格的文本节点

  •  0
  • Andrew Cooper  · 技术社区  · 7 年前

    我们有一个包含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>
    

    如何生成所需的输出?

    注意,我们仅限于XSLT1.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 回复  |  直到 5 年前
        1
  •  0
  •   zx485 potemkin    7 年前

    如何在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>
    
        2
  •  0
  •   Andrew Cooper    7 年前

    我可以通过向样式表添加一个脚本块来构建新行分隔值来实现这一点。

    我仍然有兴趣知道纯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>