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

时间:2012-06-25 03:25:47

标签: xslt-1.0

我想调用并模板并将输出保存到变量中。我想保存输出的 ALL ,包括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而不是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中处理JSON,我似乎一直在暗示我没有做正确的事情。 / HTML。

任何人都可以帮我解决问题吗?当我在JSONItemDetails中调用HTML标记来创建我的JSON字符串时,为什么要从DataTableRow的输出中删除HTML标记?

编辑#2:

我的代码中发生了一些事情,导致HTML标记被剥离的问题。我终于能够解决导致问题的原因,现在我一直试图弄清楚如何解决这个问题。这是附加说明。

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

  1. xsl-copy不能用于在标记的值=属性下输出。我理解为什么,虽然如果我按照我的方向前进,我仍然不知道如何处理这个问题。
  2. 即使我找到#1的解决方案,我也必须弄清楚如何在HTML标记中转义双引号。即:双引号在value =属性内无效。我理解撇号可以工作,但这些双引号是通过在某些标签下使用xsl:attribute来指定类或偶然风格而创建的。我有一个模板,用反斜杠转义双引号,但调用它也剥离了我的HTML标签,我不知道为什么 - 所以我不知道如何解决它。我将在下面发布此模板的代码。
  3. 如果我针对上述两个问题找到了解决办法,我可以朝着我前进的方向前进,但我愿意听取有关为什么我不应该将数据与显示混合的建议在我的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没有进入图片,因为页面上没有显示任何控件来改变产品的属性(颜色,大小等)。

    我绝对想要编码这个&#34;对&#34;,所以我当然感谢您从经验中获得的。我不认为任何人都可以有效地建议我支持或反对最终结果,而不知道让我在那里做出的所有考虑因素

    以下是用于转义双引号的模板:

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

    这是来自渲染网页的粘贴。我收到了#34;未终止的字符串文字错误&#34;并且它在第一行<tr>之前指向撇号。

    我假设这是因为字符串的空格分布在几行上。制作调用模板时,空格是原样的。我不确定如何禁用这个whitespacing,或者如果它甚至会产生影响。我会在谷歌上做一些谷歌搜索,但是有没有人知道是否有办法为调用模板关闭空格?

2 个答案:

答案 0 :(得分:7)

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

另请注意,<xsl:value-of />(和<xsl:copy-of />相关)应位于模板内,而不是<xsl:stylesheet >....</xsl:stylesheet>的根级别 - 在我的XSLT调试器中引发错误< / p>

所以对我有用的测试是

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

只要你的房产名称中没有空格

答案 1 :(得分:1)

<xsl:value-of select="$test"/>

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

By definition xsl:value-of输出在select属性中评估表达式的结果的字符串值。

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

  

xsl:copy-of元素可用于将节点集复制到   结果树没有将其转换为字符串

在XSLT 2.0中,xsl:sequence可用于按任意顺序输出节点集的节点