我有一个包含信息的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替换为正确的值。
答案 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('<', local-name(), '/>')" disable-output-escaping="yes"/>
</xsl:message>
<xsl:value-of select="concat('<', local-name(), '/>')"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
请注意,这是一种不同的方法,因为它使用模板作为输入文档而不是变量列表。为什么?对我来说,使用字母模板作为输入是更有意义的,因为它实际上是你转换并获得输出的文档。