6

我想调用模板并将输出保存到变量中。我想保存包括 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 行数据进行了大量决策。有 2 列,一列用于标签,另一列用于数据。

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

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

所以我有一个模板,它可以对所有这些先决条件进行任何化处理,并构造一个<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它几乎与使用over无关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。DataTableRow 从 JSONItemDetails 模板调用,而不是得到以下结果:

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

我得到的结果

Some LabelSome Data

提出这样一个简单问题的信息非常多,但我收到的回复似乎暗示我在处理 XSLT/HTML 中的 JSON 时做的不对。

谁能帮我解决我的问题?当我在 JSONItemDetails 中调用它以创建我的 JSON 字符串时,为什么从 DataTableRow 的输出中去除 HTML 标记?

编辑#2:

我的代码发生了几件事,这些事情导致了 HTML 标签被剥离的问题。我终于弄清了导致问题的原因,现在我一直在尝试找出解决问题的方法。以下是附加说明。

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

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

如果我对上述两个问题有解决方法,我可以按照我一直在走的方向继续前进,但我愿意听取关于为什么我不应该在我的 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>颜色规格和字体大小,甚至特定表格行是 adisplay:block还是 a 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. 不幸的是,使用<内部 axsl:text无效并产生错误。所以我改变了<to&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>- 在第二行。

我假设这是因为字符串有空格将其分布在几行上。空白是来自制作调用模板的原样。我不确定如何禁用此空白,或者这是否会有所作为。我将对此进行一些谷歌搜索,但有谁知道是否有办法为调用模板关闭空格?

4

2 回答 2

12

尝试<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 中,所以该值将与节点的值相同,即标签之间的数据。Copy of 将准确输出完整的副本、标签和所有


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

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

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

于 2012-06-25T03:58:42.683 回答
2
<xsl:value-of select="$test"/>

您必须使用xsl:copy-of(或xsl:sequence在 XSLT 2.0 中推荐) -- 而不是xsl:value-of.

根据定义,属性xsl:value-of中表达式的求值结果的字符串值select

<xsl:copy-of>select输出(按文档顺序)在其属性中指定的节点集的所有节点。或者,再次引用 W3C XSLT 1.0 规范:

" xsl:copy-of 元素可用于将节点集复制到结果树,而无需将其转换为字符串"

在 XSLT 2.0xsl:sequence中,可用于以任何所需的顺序输出节点集的节点

于 2012-06-25T03:53:49.803 回答