简体   繁体   中英

Insert opening and closing HTML tags independtly using XSLT

I have used to the following variable:

<xsl:variable name="openTag">
<![CDATA[
<div>
]]>
</xsl:variable>

<xsl:variable name="closeTag">
<![CDATA[
</div>
]]>
</xsl:variable>

And implemented in the following way:

<div class="root">
      <xsl:variable name="ownerid" select="Prp[@name='owner id']/@value" />
      <xsl:variable name="nextownerid" select="preceding-sibling::Node[1]/Prp[@name='owner id']/@value"/>

      <xsl:choose>
        <xsl:when test="$ownerid = '-1'">
          <xsl:copy-of select="$LogText"/>
        </xsl:when>
        <xsl:when test="$ownerid &lt; $nextownerid">
          <xsl:copy-of select="$openTag"/>
          <xsl:copy-of select="$LogText"/>
        </xsl:when>

        <xsl:when test="$ownerid &gt; $nextownerid">
          <xsl:copy-of select="$openTag"/>
          <xsl:copy-of select="$LogText"/>
          <xsl:copy-of select="$closeTag"/>
          <xsl:copy-of select="$closeTag"/>
        </xsl:when>

        <xsl:otherwise>
          <xsl:copy-of select="$openTag"/>
          <xsl:copy-of select="$LogText"/>
          <xsl:copy-of select="$closeTag"/>
        </xsl:otherwise>
      </xsl:choose>
    </div>

The problem is that div tags are outputted as text and not recognized as HTML tags. Is there any workaround?

XSLT is a tree transformation language: it takes a tree of nodes as input, and produces a tree of nodes as output. It doesn't read lexical XML containing start and end tags, and it doesn't output lexical XML containing start and end tags. The input tree is constructed (usually) from lexical XML by a separate processor called an XML parser, and the output tree is (often) turned into lexical XML by a separate processor called an XML serializer.

disable-output-escaping is a hack whereby the XSLT processor sends out-of-band information to the serializer. As such it creates an undesirable close-coupling between the transformer and the serializer, which prevents your XSLT code working in a pipeline where there is no serializer (for example, Firefox).

It looks as if your logic is trying to tackle a "group-adjacent" problem: group all adjacent elements having the same owner ID. There are much better ways of tackling that problem in XSLT without using serialization hacks. In XSLT 2.0:

<xsl:for-each-group select="*" group-adjacent="owner-id">
  <div>
    <xsl:copy-of select="current-group()"/>
  </div>
</xsl:for-each-group>

It's a bit trickier if you're constrained to XSLT 1.0, but not hard: look for examples of "sibling recursion".

This is the XSLT 1.0 transformation that you are actually looking for.

It creates a nicely nested tree of well-formed HTML without any disable-output-escaping trickery.

<xsl:stylesheet 
  version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
  <xsl:output method="html" indent="yes" />

  <!-- index Nodes by their owner ID -->
  <xsl:key name="kNodeByOwnerId" match="Node" use="Prp[@name = 'owner id']/@value" />

  <xsl:template match="/">
    <html>
      <head>
        <style type="text/css"><![CDATA[
          body {
            font-size: 14px;
            font-family: verdana;
            background-color: white;
          }
          div.root {
            padding: 0px 0px 2px 0px;
          }
          div.root div {
            padding: 0px 0px 0px 0px;
            margin-left: 3em;
          }
          div.remark {
            margin-left: 2em;
          }
          img.icon {
            padding-right: 5px;
          }
          span.log {
            font-weight: bold;
          }
          span.log.fail {
            color: red;
          }
          span.log.pass {
            color: green;
          }
          span.log.info {
            color: blue;
          }
        ]]></style>
      </head>
      <body>
        <!-- output "top level" nodes, i.e. those with owner ID -1 -->
        <xsl:apply-templates select="key('kNodeByOwnerId', '-1')">
          <xsl:sort select="substring-after(@name, 'message ')" data-type="number" />
          <xsl:with-param name="containerClass" select="'root'" />
        </xsl:apply-templates>
      </body>
    </html>
  </xsl:template>

  <xsl:template match="Node">
    <xsl:param name="containerClass" select="''" />

    <xsl:variable name="messageClass">
      <xsl:apply-templates select="Prp[@name = 'type']" />
    </xsl:variable>

    <div class="{$containerClass}">
      <img class="icon" src="./{$messageClass}.png" />

      <span class="log {$messageClass}">
        <xsl:value-of select="Prp[@name='message']/@value"/>
      </span>

      <xsl:apply-templates select="Prp[@name = 'remarks']" />

      <!-- output nodes that belong to this node (recursive!) -->
      <xsl:apply-templates select="key('kNodeByOwnerId', Prp[@name = 'id']/@value)">
        <xsl:sort select="substring-after(@name, 'message ')" data-type="number" />
      </xsl:apply-templates>
    </div>
  </xsl:template>

  <xsl:template match="Prp[@name = 'remarks']">
    <xsl:if test="normalize-space(@value) != ''">
      <div class="remark">              
        <img class="icon" src="./info.png" />
        <xsl:value-of select="@value"/>              
      </div>
    </xsl:if>
  </xsl:template>

  <xsl:template match="Prp[@name = 'type']">
    <xsl:choose>
      <xsl:when test="@value = '0'">pass</xsl:when>
      <xsl:when test="@value = '4'">info</xsl:when>
      <xsl:otherwise>fail</xsl:otherwise>
    </xsl:choose>
  </xsl:template>

</xsl:stylesheet>

Note the use of an <xsl:key> . A thorough explanation how keys work can be found in an earlier answer of mine .

The code is pretty straight-forward, so you should have little trouble understanding what it does. Don't hesitate to ask if anything is unclear.

I'd strongly recommend putting the CSS code into a separate file.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM