как заполнять текстовые шаблоны с помощью xslt - PullRequest
3 голосов
/ 21 октября 2010

У меня есть 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 ]

6 голосов
/ 21 октября 2010

Эта таблица стилей:

<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, с XSLT? и Макеты XSLT с областью динамического содержимого

3 голосов
/ 21 октября 2010

Одним из возможных решений было бы изменить файл шаблона на конфигурацию 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>

Когда XSLT запускается для образца XML-файла, он выдает следующий вывод:

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 голосов
/ 21 октября 2010

Вы можете сделать это:

<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
1 голос
/ 21 октября 2010

Как подсказывает @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 в качестве входных данных, это один из возможных способов его решения. Разные значения для заполнения вашего шаблона, конечно, могут быть получены из внешнего XML-файла с fn:document() aswell:

<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>

Обратите внимание, что это другой подход, поскольку он использует шаблон в качестве входного документа, а не список переменных. Зачем? Для меня более логично использовать шаблон письма в качестве входных данных, поскольку именно этот документ вы преобразовываете и получаете в качестве выходных данных.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...