如何使用xslt填充文本模板

时间:2010-10-21 10:19:54

标签: xml xslt replace

我有一个包含信息的XML文件,例如:

<letter>
  <name>Test</name>
  <age>20</age>
  <me>Me</me>
</letter>

然后我有一个文本模板,如:

Dear $name,

some text with other variables like $age or $name again

greatings $me

当使用xslt将XML转换为纯文本字母时,我可以使用以下内容:

<xsl:text>Dear </xsl:text><xsl:value-of select="name"/><xsl:text>

some text with other variables like </xsl:text>
<xsl:value-of select="age"/><xsl:text> or </xsl:text>
<xsl:value-of select="name"/><xsl:text> again

greatings </xsl:text><xsl:value-of select="me"/>

但是当我获得越来越多的变量和更多的文本时,这将成为进入和维护的噩梦。

有没有办法用xslt以更干净的方式做到这一点?我更愿意,如果我可以使用我上面使用的文本模板,并将$ name和$ age替换为正确的值。

4 个答案:

答案 0 :(得分:6)

此样式表:

<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:my="my">
    <xsl:output method="text"/>
    <xsl:preserve-space elements="my:layout"/>
    <my:layout>Dear <name/>,

some text with other variables like <age/> or <name/> again

greatings <me/></my:layout>
    <xsl:variable name="vData" select="/"/>
    <xsl:template match="/">
        <xsl:apply-templates select="document('')/*/my:layout/node()"/>
    </xsl:template>
    <xsl:template match="*/*">
        <xsl:value-of select="$vData//*[name()=name(current())]"/>
    </xsl:template>
</xsl:stylesheet>

输出:

Dear Test,

some text with other variables like 20 or Test again

greatings Me

注意:对于更复杂的人口模式(即迭代),请查看以下帖子:Sitemesh like functionality with XSLT?XSLT Layouts With Dynamic Content Region

答案 1 :(得分:3)

一种可能的解决方案是将模板文件更改为XML配置,如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<template>
    Dear <name/>,

    some text with other variables like <age/> or <name/> again

    greatings <me/>
</template>

假设上面的XML模板名为template.xml并且与下面的XSLT位于同一目录中:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:xd="http://www.oxygenxml.com/ns/doc/xsl"
    exclude-result-prefixes="xs xd"
    version="1.0">
<xsl:output method="text"/>

<!--Load the template document as a variable -->
<xsl:variable name="templateFile" select="document('template.xml')" />
<!--Load the current document in a variable, so that we can reference this file from within template matches on the template.xml content-->    
<xsl:variable name="letter" select="/*" />    

<!--When this stylesheet is invoked, apply templates for the templateFile content (everything inside the template element) -->    
<xsl:template match="/">
    <xsl:apply-templates select="$templateFile/*/node()" />
</xsl:template>

 <!--Match on any of the template placeholder elements and replace with the value from it's corresponding letter document-->
<xsl:template match="template/*">
    <!--set the local-name of the current element as a variable, so that we can use it in the expression below -->
    <xsl:variable name="templateElementName" select="local-name(.)" />
    <!--Find the corresponding letter element that matches this template element placeholder-->
    <xsl:value-of select="$letter/*[local-name()=$templateElementName]" />
</xsl:template>

<!--standard identity template that copies content forward-->
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()" />
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

当针对示例XML文件运行XSLT时,它会生成以下输出:

Dear Test,

some text with other variables like 20 or Test again

greatings Me

正如@Tomalak指出的那样,将从输出中删除不匹配的占位符元素。如果要保留它们,为了使XML文件显然与模板中的占位符项不匹配,您可以更改模板占位符元素上匹配的模板,如下所示:

 <!--Match on any of the template placeholder elements and replace with the value from it's corresponding letter document-->
<xsl:template match="template/*">
    <!--set the local-name of the current element as a variable, so that we can use it in the expression below -->
    <xsl:variable name="templateElementName" select="local-name(.)" />
    <!--Find the corresponding letter element that matches this template element placeholder-->
    <xsl:variable name="replacementValue" select="$letter/*[local-name()=$templateElementName]" />
    <xsl:choose>
        <xsl:when test="$replacementValue">
            <xsl:value-of select="$replacementValue" />
        </xsl:when>
        <xsl:otherwise>
            <xsl:text>$</xsl:text>
            <xsl:value-of select="local-name()"/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

如果有一个不匹配的占位符元素,例如<foo/>,则它会在文本输出中显示为$foo

答案 2 :(得分:2)

你可以这样做:

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

  <xsl:variable name="placeholderText"
>Dear $name,

some text with other variables like $age or $name again,
the $undefined will not be replaced.

greatings $me</xsl:variable>

  <xsl:template match="letter">
    <xsl:call-template name="expand-placeholders">
      <xsl:with-param name="text"   select="$placeholderText" /> 
      <xsl:with-param name="values" select="*" /> 
    </xsl:call-template>
  </xsl:template>

  <xsl:template name="expand-placeholders">
    <xsl:param name="text"   select="''" />
    <xsl:param name="values" select="false()" />
    <xsl:choose>
      <xsl:when test="contains($text, '$') and $values">
        <xsl:variable name="head" select="substring-before($text, '$')" />
        <xsl:variable name="curr" select="substring-after($text, '$')" />
        <!-- find the longest matching value name... -->
        <xsl:variable name="valName">
          <xsl:for-each select="$values[starts-with($curr, name())]">
            <xsl:sort select="string-length(name())" data-type="number" order="descending" />
            <xsl:if test="position() = 1">
              <xsl:value-of select="name()" />
            </xsl:if>
          </xsl:for-each>
        </xsl:variable>
        <!-- ... and select the appropriate placeholder element -->
        <xsl:variable name="val" select="$values[name() = $valName][1]" />
        <xsl:variable name="tail">
          <xsl:choose>
            <xsl:when test="$val">
              <xsl:value-of select="substring-after($curr, name($val))" />
            </xsl:when>
            <xsl:otherwise>
              <xsl:value-of select="$curr" />
            </xsl:otherwise>
          </xsl:choose>
        </xsl:variable>

        <xsl:value-of select="$head" />
        <xsl:if test="not($val)">$</xsl:if>
        <xsl:value-of select="$val" />  

        <xsl:call-template name="expand-placeholders">
          <xsl:with-param name="text"   select="$tail" /> 
          <xsl:with-param name="values" select="$values" /> 
        </xsl:call-template>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="$text" />  
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>

针对您的XML示例的输出:

Dear Test,

some text with other variables like 20 or Test again,
the $undefined will not be replaced.

greatings Me

答案 3 :(得分:1)

正如@Mads Hansen建议的那样,如果你可以使用基于XML的模板,这可以通过更简单,更好的方式解决。

这是一个以XML模板作为输入的解决方案。

XML模板作为输入:

<?xml version="1.0" encoding="UTF-8"?>
<letter>Dear <name/>,

some text with other variables like <age/> or <name/> again

greatings <me/></letter>

<强> XSLT:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:vars="my.variables">

  <xsl:output method="text"/>
  <xsl:preserve-space elements="letter"/>

  <vars:letter>
    <name>Test</name>
    <age>20</age>
    <me>Me</me>
  </vars:letter>

  <xsl:template match="name|age|me">
    <xsl:variable name="name" select="local-name()"/>
    <xsl:value-of select="document('')/*/vars:letter/*[local-name() = $name]"/>
  </xsl:template>

</xsl:stylesheet>

<强>输出:

Dear Test,

some text with other variables like 20 or Test again

greatings Me

如果您使用XML模板作为输入,这是解决它的一种可能方法。填充模板的不同值当然可以从带有fn:document()的外部XML文件中获取:

<xsl:value-of select="document('path/to/file.xml')/letter/*[local-name() = $name]"/>

更新:由于@Tomlak评论说上述解决方案不是很灵活,所以这里有更新的解决方案:

<强> XSLT:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:vars="my.variables">

  <xsl:output method="text"/>
  <xsl:preserve-space elements="letter"/>

  <vars:letter>
    <name>Test</name>
    <age>20</age>
    <me>Me</me>
  </vars:letter>

  <xsl:template match="letter">
    <xsl:apply-templates/>
  </xsl:template>

  <xsl:template match="letter/*">
    <xsl:variable name="name" select="local-name()"/>
    <xsl:variable name="content" select="document('')/*/vars:letter/*[local-name() = $name]"/>
    <xsl:value-of select="$content"/>
    <xsl:if test="not($content)">
      <xsl:message>
        <xsl:text>Found unknown variable in template: </xsl:text>
        <xsl:value-of select="concat('&lt;', local-name(), '/&gt;')" disable-output-escaping="yes"/>
      </xsl:message>
      <xsl:value-of select="concat('&lt;', local-name(), '/&gt;')"/>
    </xsl:if>
  </xsl:template>

</xsl:stylesheet>

请注意,这是一种不同的方法,因为它使用模板作为输入文档而不是变量列表。为什么?对我来说,使用字母模板作为输入是更有意义的,因为它实际上是你转换并获得输出的文档。