xslt-1.0 - 调用XSLT模板并将所有输出保存到变量

标签 xslt-1.0

我想调用和模板并将输出保存到变量。我想保存包括HTML标记在内的所有输出,但是事实并非如此。

例如,使用以下简单的XSLT:

<xsl:call-template name="demonstration">

<xsl:template name="demonstration">
    <p>Just a test</p>
</xsl:template>


这个简单的模板会将<p>Just a test</p>输出到HTML页面。如果我查看页面源代码,则可以看到(当然)。现在以使用相同模板的示例为例,但我不想将其保存为变量,而不仅仅是输出为HTML:

<xsl:variable name="test">
    <xsl:call-template name="demonstration">
</xsl:variable>
<xsl:value-of select="$test"/>


<xsl:template name="demonstration">
    <p>Just a test</p>
</xsl:template>


查看变量显示,现在唯一的输出是Just a test

HTML标记在哪里?我希望能够将调用模板的输出保存到变量,但是我也需要HTML标记。

有没有办法避免在调用这样的模板时丢失HTML标签?有写模板的其他方法吗?也许我缺少一个设置?我试过disable-escape-encoding,但这没什么区别(至少在Safari中)。

我更喜欢将模板用于两种需求:我希望能够调用它,并将输出保存在HTML页面中以供查看。我还希望能够将调用包装在一个变量中,但是重要的是,这两种调用方法都包括模板中所指定的所有HTML标记/标记。

编辑:

到目前为止,我已经尝试过两个发布的答案,但是copy-of给我的结果与value-of相同。实际上,我没有使用value-of,仅显示了如何重复该问题。这是我要做什么的更详尽的解释。

有一个样式表,该样式表用于转换从REST响应接收到的相当大的XML。样式表的输出方法设置为html。

该样式表中的模板之一对如何在一个表中显示多达4行数据做出了很多决策。有两列,一列用于标签,另一列用于数据。

所做的决策包括标签的文本是什么以及类别是什么-有时标签是绿色,有时是红色,等等。数据列可以包含文本和/或数字。某些文本可能为粗体,某些可能为彩色。有很多先决条件可以确定这些属性。

如果要显示单个项目的详细信息,则可以使用此模板完成操作,但是其中一个项目可以选择多个属性。例如,可能有大小和颜色。根据用户选择的尺寸或颜色,价格可能会有所不同,特定商品可能缺货,如果价格不同,则对用户有不同的节省。不同的物品可能免费送货,或者仅可用于预购。有很多属性。

因此,我有一个模板,可以化解所有这些先决条件,并构造一个<tr><td></td><td></td></tr>,其中填充了一些不太简单的逻辑和派生的简单文本和数据。

我已经对该模板进行了模块化,以便可以为任何一项调用它。它被参数化,因此我可以指定标签文本,数据,类以及其他一些东西。

加载网页时,将显示默认/主要项目以及常规信息,例如价格范围和储蓄范围等。

但是,当用户选择颜色或大小时,这些表行需要使用正确的数据进行更新。我已经处理过XML中的数据,并且发出另一个服务器请求将非常昂贵,因此我要做的是创建一个JSON字符串数组,其中包含所有不同种类项目的所有数据。我将此JSON保存在隐藏的输入控件的value属性中。是的,还有其他方法可以这样做,但是对我来说似乎可以管理。

由于创建这些表行的逻辑在服务器上的样式表中,因此我对所有项目执行所有此逻辑,然后将计算出的字符串传递给客户端。这是一个看起来像这样的例子:

<input id="hfData" type="hidden" value="[
{"ProductID": "00001", "Color": "Beige", "Size": "14"},
{"ProductID": "00002", "Color": "Black", "Size": "14"},
{"ProductID": "00003", "Color": "Blue", "Size": "10"},
{"ProductID": "00004", "Color": "Pink", "Size": "10"},
{"ProductID": "00005", "Color": "Yellow", "Size": "10"}
]"
/>


然后,我有了一个小的JQuery脚本,只要用户更改下拉列表选择或更改属性,就会触发该脚本。该脚本解析上述JSON并确定当前配置的ProductID是什么。

注意:由于如果我用这样的双引号引起来,输入控件的value属性将无效,因此它们实际上都是&quot;。我在这里显示双引号以便于查看。

确定ProductID后,页面将使用此编辑中前面介绍的许多不同的详细信息进行更新。这些细节来自我创建的另一个JSON对象-再次,因为所有产品细节在XSLT样式表中都是已知的,这就是我创建JSON字符串数组的地方。

<input id="hfDetails" type="hidden" value="[
{"ProductID": "00001", "ListPrice": "<tr class="collapsed"><td class="datalabel">List Price:</td><td class="datainfo"></td></tr>", "YourPrice": "<tr><td class="datalabel">Price:</td><td class="datainfo">$23.99 & is elegible for <span class="freeshipping">FREE</span> shipping <a href="#">Details</a></td></tr>"},

... etc, etc, etc ...

]"
/>


如您所见,上方的隐藏输入具有一个value属性,该属性包含一个包含HTML标记的JSON对象。我已经看过这项工作,但是为了使JSON正常工作,我必须转义所有标签。 &号必须为&amp;,然后为&lt;&gt;,并且所有引号都使用反斜杠转义。我已经用这种方式进行了编码并且可以正常工作-尽管看上去很讨厌,但是可以正常工作,并且它使我不必往返于服务器-可以防止我将所有逻辑放在客户端上创建这些字符串。

这些都与我遇到的问题无关,但是我希望摆脱JSON和XSL / XML教授的所有评论,这些教授向我提出挑战,让我在一个句子中使用JSON和XSLT(或HTML) ...

现在,我希望我可以(非常简单地)显示我遇到的确切问题。首先,它与JSON的关系非常小。它实际上与在xsl:value-of上使用xsl:copy-of无关。

在这里,我创建一个隐藏的输入字段,其value属性包含一个JSON字符串:

<input>
  <xsl:attribute name="id">
    <xsl:value-of select="$id"/>
  </xsl:attribute>
  <xsl:attribute name="type">
    <xsl:text>hidden</xsl:text>
  </xsl:attribute>
  <xsl:attribute name="runat">
    <xsl:text>server</xsl:text>
  </xsl:attribute>
  <xsl:attribute name="value">
    <xsl:text>[</xsl:text>

    <xsl:for-each select="Items/Item">
          <xsl:call-template name="JSONItemDetails">
            <xsl:with-param name="offerNode" select="Offers"/>
            <xsl:with-param name="lastitem" select="position() = last()"/>
          </xsl:call-template>
    </xsl:for-each>

    <xsl:text>]</xsl:text>
  </xsl:attribute>
</input>


<xsl:template name="JSONItemDetails">
    <xsl:param name="offerNode" select="."/>
    <xsl:param name="attributesNode" select="ItemAttributes"/>
    <xsl:param name="listprice" select="0"/>
    <xsl:param name="lastitem" select="false()"/>

    <!-- Product ID -->
    <xsl:text>{</xsl:text>
    <xsl:text>&quot;ProductID&quot;: </xsl:text>
    <xsl:text>&quot;</xsl:text>
    <xsl:value-of select="./ProductID"/>
    <xsl:text>&quot;,</xsl:text>

    <xsl:for-each select="msxml:node-set($offerNode)">

        <!-- Title -->
        <xsl:text>&quot;Title&quot;: </xsl:text>
        <xsl:text>&quot;</xsl:text>
        <xsl:call-template name="escapeQuote">
            <xsl:with-param name="pText">
            <xsl:call-template name="title">
                <xsl:with-param name="node" select="$attributesNode" />
            </xsl:call-template>
            </xsl:with-param>
        </xsl:call-template>
        <xsl:text>&quot;,</xsl:text>

        <!-- List Price -->
        <xsl:text>&quot;ListPrice&quot;: </xsl:text>
        <xsl:text>&quot;</xsl:text>
        <xsl:call-template name="escapeQuote">
            <xsl:with-param name="pText">
            <xsl:call-template name="DataTableRow">
                <xsl:with-param name="label" select="'List Price:'" />
                <xsl:with-param name="data" select="./Price/FormattedPrice" />
                <xsl:with-param name="dataid" select="'listprice'" />
            </xsl:call-template>
            </xsl:with-param>
        </xsl:call-template>
        <xsl:text>&quot;</xsl:text>

    </xsl:for-each>

    <xsl:text>}</xsl:text>
    <xsl:if test="$lastitem != true()">
        <xsl:text>,</xsl:text>
    </xsl:if>




DataTableRow模板做了很多事情,并且大多数情况下提供了一致性,但是也清除了用于创建行和列的所有杂散HTML标记。这是该模板。

<xsl:template name="DataTableRow">
    <xsl:param name="label" select="''"/>
    <xsl:param name="data" select="''"/>
    <xsl:param name="dataid" select="''"/>
    <xsl:param name="concat" select="''"/>
    <xsl:param name="class" select="''"/>

    <tr>
        <xsl:choose>
        <xsl:when test="$data = not(string(.))">
          <xsl:attribute name="style">
            <xsl:text>display:none</xsl:text>
          </xsl:attribute>
        </xsl:when>
        <xsl:otherwise>
           <xsl:attribute name="style">
           <xsl:text>display:block</xsl:text>
        </xsl:attribute>
        </xsl:otherwise>
        </xsl:choose>

        <!-- Label Column -->
        <td>
            <div>
                <xsl:choose>
                <xsl:when test="$class = 'bigmaroon'">
                    <xsl:attribute name="class">
                        <xsl:text>datalabel maroon</xsl:text>
                    </xsl:attribute>
               </xsl:when>
               <xsl:otherwise>
                    <xsl:attribute name="class">
                       <xsl:text>datalabel</xsl:text>
                    </xsl:attribute>
               </xsl:otherwise>
               </xsl:choose>
               <xsl:value-of select="$label"/>
           </div>
        </td>

        <!-- Data Column -->
        <td class="datainfo">
            <xsl:attribute name="id">
                <xsl:value-of select="$dataid"/>
            </xsl:attribute>

            <xsl:choose>
            <xsl:when test="$class = 'strike'">
                <strike>
                    <xsl:value-of select="$data" />
                </strike>
                <xsl:value-of select="$concat"/>
            </xsl:when>
            <xsl:when test="$class = 'maroon'">
                <span class="maroon">
                    <xsl:value-of select="$data" />
                </span>
                <xsl:value-of select="$concat"/>
            </xsl:when>
            <xsl:when test="$class = 'bigmaroon'">
                <span class="bigmaroon">
                    <xsl:value-of select="$data" />
                </span>
                <xsl:value-of select="$concat"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="$data" />
                <xsl:value-of select="$concat" />
            </xsl:otherwise>
            </xsl:choose>
        </td>
    </tr>




DataTableRow模板有一些逻辑,每次我需要写这些行之一时,都会做得很麻烦。首先,所有这些行都必须存在于HTML中。如果没有数据,则将它们设置为display:none样式。这很重要,因为除非有有效的选择器,否则我将无法用JQuery脚本中的数据填充它们。

问题是,毕竟,直接像这样调用DataTableRow可以正常工作:

<xsl:call-template name="DataTableRow"/>


在上面的调用中,HTML标记全部输出到我的网页。他们看起来不错,他们的班级和风格都是正确的。我可以在模板的任何地方这样称呼它,并且效果很好。我确定这里或那里都存在错误,并且可能会看到一种更好/更高效的编码方式,但是它基本上可以正常工作并且适用于HTML。

问题:在上面构建隐藏的输入字段时,我无法获得任何HTML标记。存储在我的JSON字符串中的唯一值是innerhtml。从JSONItemDetails模板调用DataTableRow,而不是得到以下结果:

<tr><td>Some Label</td><td>Some Data</td></tr>


我得到了一个结果

Some LabelSome Data


这是一个非常简单的问题,它提供了大量的信息,但是我得到的反馈似乎暗示着,如果我在XSLT / HTML中处理JSON,那我做的事情就不正确。

任何人都可以帮助解决我的问题吗?在JSONItemDetails中创建HTML标记时,为什么从DataTableRow的输出中剥离HTML标记?

编辑2:

我的代码中发生了几件事,这些问题导致剥夺了HTML标签的问题。终于可以解决导致问题的原因,现在我一直在尝试找出解决问题的方法。这是其他说明。

能够确认,xsl:value-of正在从模板调用的输出中剥离HTML标记。 xsl:copy-of显示我期望的标签。这对我了解问题的根源有很大帮助,但也使我能够确定其他问题。


xsl-copy不能用于在标签的value =属性下输出。我理解为什么,但是如果我朝前进的方向前进,我对如何处理这个问题一无所知。
即使我找到了解决#1的方法,也必须弄清楚如何在HTML标记中转义双引号。即:在value =属性中,双引号无效。我知道撇号会起作用,但是这些双引号是通过在某些标签下使用xsl:attribute指定类或特殊样式创建的。我有一个模板,可以用反斜杠转义双引号,但是调用它也会剥去我的HTML标记,而且我看不到为什么-所以我不知道如何解决它。我将在下面发布此模板的代码。


如果我有解决上述2个问题的方法,则可以按照我所追求的方向进行操作,但是我乐于听取有关为什么不应该在JSON中混合显示数据的建议。

我将两者混合在一起是因为我不喜欢将显示逻辑放在我的JQuery脚本中的想法。我希望我的脚本对这种逻辑仍然一无所知。在JQuery中,使用这些JSON对象来向下钻取到ProductID并替换这样的表行很简单:

        var details = $.parseJSON($("#[id*='hfDetails']").val());

        var filteredDetails = $.grep(details, function (n) {
            return n.ProductID == ProductID;
        });

        if (filteredDetails.length > 0) {
            var product = filteredDetails[0];

            $("div#title").html(product.Title);

            $("td#listprice").parent().html(product.ListPrice);
            $("td#price").parent().html(product.Price);
            $("td#yousave").parent().html(product.YouSave);
        }


如果我按照Dimitre的建议从JSON字符串中删除显示内容,突然间我不得不在jquery脚本中加入很多逻辑,不仅要提供<tr><td>格式(类),还要包含包装实际数据,例如<strike<strong>,颜色规范和字体大小,甚至特定表行是display:block还是display:none。我绝对不想在客户端的JQuery脚本中编写任何逻辑代码。

在我的XSLT模板中,这是非常简单的xsl:for-each,它可以在服务器上创建这些字符串并将结果填充到JSON对象中,以便我可以使用上述脚本。不可否认,要看的数据很难看。

另一个要注意的是,无论我是否将显示与数据分开,如果只有一个项目具有单一显示可能性,则仍需要用于处理XML的当前XSLT模板。如果只有一个产品,则JSON不会出现在图片中,因为页面上没有显示控件来更改产品的属性(颜色,大小等)。

我绝对想对这种“正确的”进行编码,因此,我当然很高兴听到经验。我只是认为,在不知道使我到达那里的所有考虑因素的情况下,没有人能有效地建议我赞成或反对最终结果

这是我用来转义双引号的模板:

<!--
Escape quotes with \ for JSON strings
-->
<xsl:template name="escapeQuote">
  <xsl:param name="pText" select="."/>

  <xsl:if test="string-length($pText) >0">
    <xsl:copy-of select="substring-before(concat($pText, '&quot;'), '&quot;')"/>

    <xsl:if test="contains($pText, '&quot;')">
      <xsl:text>\"</xsl:text>

      <xsl:call-template name="escapeQuote">
        <xsl:with-param name="pText" select="substring-after($pText, '&quot;')"/>
      </xsl:call-template>
    </xsl:if>
  </xsl:if>

</xsl:template>


该模板从输入字符串中剥离HTML标记。我这样称呼它:

<xsl:variable name="output3">
    <xsl:call-template name="escapeQuote">
        <xsl:with-param name="pText">
        <xsl:call-template name="DataTableRow">
            ... with-params ...
        </xsl:call-template>
        </xsl:with-param>
    </xsl:call-template>
</xsl:variable>


立即执行:

<xsl:copy-of select="$output3"/>


显示HTML标记不再存在,因此也不存在双引号。但是,这样做会显示我所有的HTML标签:

<xsl:variable name="output3">
    <xsl:call-template name="DataTableRow">
        ... with-params ...
    </xsl:call-template>
</xsl:variable>
<xsl:copy-of select="$output3"/>


感谢您阅读所有这些内容,而且顺便说一句,我看到了其他文章,它们对XSL代码进行了很好的格式化和着色。这很容易阅读,但是我无法使其与我发布的代码一起使用。一切都是黑色文本,并左对齐。我已尝试纠正此问题,但它对我不起作用。

编辑#3:

Dimitre评论说,可以在隐藏的输入字段的value属性中使用引号,因此我做了一些实验。我看上去到处似乎都有陷阱。

<input value='something containing " literally'/>

为了使我的value=内容用撇号而不是双引号引起来,我尝试创建隐藏的<input>,如下所示:

<input>
  <xsl:attribute name="id">
    <xsl:value-of select="$id"/>
  </xsl:attribute>
  <xsl:attribute name="type">
    <xsl:text>hidden</xsl:text>
  </xsl:attribute>
  <xsl:attribute name="runat">
    <xsl:text>server</xsl:text>
  </xsl:attribute>
  <xsl:text> value='[</xsl:text>
  <xsl:copy-of select="$output"/>
  <xsl:text>]'</xsl:text>
</input>    


这是行不通的,因为<input...>标记在结束的value=之后使用</input>属性(及其内容)呈现。

所以我删除了xsl:attribute元素,并用xsl:text形成了它。不幸的是,在<中使用xsl:text是无效的,并会产生错误。因此,我将<和相应的&lt;都更改为>。这不会产生任何错误,但是会导致整个<input>及其内容在呈现页面时显示为字符串。 (我正在Firefox中查看页面)。

我没有尝试使用CDATA对此进行编码,因为我非常确定这也不起作用,并且我将获得与后者相同的结果-所有内容都显示为字符串。

耸耸肩

编辑#4:

OJay建议放弃将JSON放在隐藏字段的value =属性中的想法。我一直都在考虑这一点,但是不确定是否需要更改代码。当他展示了我几乎不需要改变的例子时,我决定去做。

不幸的是,还有另一个陷阱。

<script type="text/javascript">

var hfDetails = [{ProductID: '000001',Title: '<h3>American Apparel Sheer Jersey Chemise X-Small-Asphalt</h3>',Condition: 'New',ListPrice: '<tr style="display:block">
    <td>
      <div class="datalabel">0List Price:</div>
    </td>
    <td class="datainfo" id="listprice">$24.99</td>
  </tr>',Price: '<tr style="display:block">
    <td>
      <div class="datalabel maroon">Sale:</div>
    </td>
    <td class="datainfo" id="price"><span class="bigmaroon">$15.49</span></td>
  </tr>',YouSave: '<tr style="display:block">
    <td>
      <div class="datalabel">You Save:</div>
    </td>
    <td class="datainfo" id="yousave"><span class="maroon">$9.50 (38%)</span></td>
  </tr>'},

 ....

]
</script>


这是来自渲染网页的粘贴。我收到“未终止的字符串文字”错误,它指向第二行第一个<tr>之前的撇号。

我假设这是因为字符串具有空格,将其分布在多行上。空格是制作调用模板的原样。我不知道如何禁用此whitespacing,否则是否会有所作为。我将对此进行一些搜索,但是有人知道是否有办法为呼叫模板关闭空白吗?

最佳答案

尝试使用<xsl:copy-of select="$test">代替<xsl:value-of ... />

还要注意,<xsl:value-of />(和<xsl:copy-of />)应该在模板内,而不是<xsl:stylesheet >....</xsl:stylesheet>的根目录-在XSLT调试器中引发错误

所以对我有用的测试是

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

  <xsl:variable name="test">
    <xsl:call-template name="demonstration" />
  </xsl:variable>

  <xsl:template match="/">
    <xsl:copy-of select="$test"/>
  </xsl:template>

  <xsl:template name="demonstration">
    <p>Just a test</p>
  </xsl:template>

</xsl:stylesheet>


Value-of将获取变量的值,因为我们使用的是XML,所以该值将与节点的值相同,即标记之间的数据。的副本将完全输出完整的副本,标签和所有副本


编辑
根据我的开始评论,我的意思是这样的:

从XSLT中退出

<script type="text/javascript">
var hfDetails = [{"ProductID": "00001", "ListPrice": '<tr class="collapsed"><td class="datalabel">List Price:</td><td class="datainfo"></td></tr>', "YourPrice": '<tr><td class="datalabel">Price:</td><td class="datainfo">$23.99 & is elegible for <span class="freeshipping">FREE</span> shipping <a href="#">Details</a></td></tr>'},

... etc, etc, etc ...

];
</script>


而不是隐藏的输入字段。这将创建一个全局javascript数组对象。
本质上跳了

var details = $.parseJSON($("#[id*='hfDetails']").val());


然后你可以做

var details = hfDetails;


无需更改任何代码。

如果您担心撇号和引号的转义。可以使用其中一个在javascript中定义字符串,然后可以安全地在字符串中使用另一个字符串,即

"ListPrice" : '<tr class="collapsed"><td class="datalabel">List Price:</td><td class="datainfo"></td></tr>'


是有效的,所以也是

"ListPrice" : "<tr class='collapsed'><td class='datalabel'>List Price:</td><td class='datainfo'></td></tr>"


还请注意,您不必将对象的属性括在引号中,即

"ProductID" : "00001"


可以只是

ProductID : "00001"


只要您的属性名称中没有空格

关于xslt-1.0 - 调用XSLT模板并将所有输出保存到变量,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11183138/

相关文章:

xml - 在应用任何其他模板之前,如何将完整文档转换为小写?

xml - 如何强制将每个双引号“转义为 XML 字符实体”

xml - XSLT:更改 namespace 的值

javascript - javascript 中的大于运算符在 xslt 中不起作用

xml - 使用 XSLT 从 XML 中写入数据列与 Excel 中的行

xslt - 在 XSL 样式表标记中删除 XSL 命名空间前缀

xslt - 将十进制小时转换为小时、分钟和秒

java - 使用 java 的 XSLT 1.0 中的正则表达式

xml - 使用 xslt/c# 比较两个 xml 文件

使用分隔符时的 XML 格式编号问题