How can you deal with embedded XML tags in XSLT?

2019-01-18 11:22发布

问题:

I am using XSLT to convert XML to HTML. I am having trouble figuring out how to deal with embedded XML nodes for formatting. For example, let's say I have the XML element:

<favoriteMovie>the <i>Star Wars</i> saga</favoriteMovie>

However, during XLST, the <i> tag gets ignored, so "Star Wars" is not italicized in the HTML output. Is there a relatively simple way to fix this?

test.xml:

<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="test.html.xsl"?>
<favoriteMovies>
    <favoriteMovie>the <i>Star Wars</i> saga</favoriteMovie>
</favoriteMovies>

test.html.xsl:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="html" omit-xml-declaration="yes" />
    <xsl:template match="/">
      <html>
        <head />
          <body>
            <ul>
                <xsl:for-each select="favoriteMovies/favoriteMovie">
                    <li><xsl:value-of select="." /></li>
                </xsl:for-each>
            </ul>
          </body>
      </html>
    </xsl:template>
</xsl:stylesheet>

回答1:

However, during XLST, the <i> tag gets ignored, so "Star Wars" is not italicized in the HTML output. Is there a relatively simple way to fix this?

Your problem is here:

<ul>
  <xsl:for-each select="favoriteMovies/favoriteMovie">
    <li><xsl:value-of select="."/></li>
  </xsl:for-each>
</ul>

The <xsl:value-of> instruction is used to create a text node. In doing so it copies to the output the string value of the XPath expression specified in the select attribute of this XSLT instruction. The string value of an element is the concatenation of all its text-node descendents.

So this is how you get the reported output.

Solution:

Use the <xsl:copy-of> instruction, which copies all the nodes that are specified in its select attribute:

<ul>
  <xsl:for-each select="favoriteMovies/favoriteMovie">
    <li><xsl:copy-of select="node()"/></li>
  </xsl:for-each>
</ul>

Another solution, more alligned with the principles of XSLT avoids using <xsl:for-each> at all:

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

 <xsl:template match="/">
  <html>
    <head />
    <body>
     <xsl:apply-templates/>
    </body>
  </html>
 </xsl:template>

 <xsl:template match="/*">
  <ul>
   <xsl:apply-templates/>
  </ul>
 </xsl:template>

 <xsl:template match="favoriteMovie">
  <li><xsl:copy-of select="node()"/></li>
 </xsl:template>
</xsl:stylesheet>

When any of the two solutions defined above are applied to the provided XML document:

<favoriteMovies>
    <favoriteMovie>the 
        <i>Star Wars</i> saga
    </favoriteMovie>
</favoriteMovies>

the wanted, correct result is produced:

<html>
    <head/>
    <body>
        <ul>
            <li>the 
                <i>Star Wars</i> saga
            </li>
        </ul>
    </body>
</html>


回答2:

You should use xsl:copy to copy the i node .

http://msdn.microsoft.com/en-us/library/ms256128.aspx

    <?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="html" omit-xml-declaration="yes" />
    <xsl:template match="/">
      <html>
        <head />
          <body>
            <xsl:apply-templates></xsl:apply-templates>        
          </body>
      </html>
    </xsl:template>
<xsl:template match="favoriteMovies">
  <ul>
    <xsl:apply-templates></xsl:apply-templates>
  </ul>  
</xsl:template>
  <xsl:template match="favoriteMovie">
    <li>      
      <xsl:apply-templates></xsl:apply-templates>
    </li>
  </xsl:template>
  <xsl:template match="i">
    <xsl:copy>
      <xsl:apply-templates/>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>


回答3:

You should use 'disable-output-escaping' attribute. The general format of element is:

<xsl:value-of select="expression" disable-output-escaping="yes|no" />

'disable-output-escaping' is optional. "yes" indicates that special characters (like "<") should be output as is. "no" indicates that special characters (like "<") should be output as "<". Default is "no".

Therefore just change your code to:

<xsl:template match="favoriteMovie">
  <xsl:copy-of select="node()" disable-output-escaping="yes"/>
</xsl:template>


回答4:

Two things to note.

First. Make sure tags are screened in CDATA

<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="test.html.xsl"?>
<favoriteMovies>
    <favoriteMovie><![CDATA[the <i>Star Wars</i> saga]]></favoriteMovie>
</favoriteMovies>

Second. Disable output escaping:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" omit-xml-declaration="yes" />
<xsl:template match="/">
  <html>
    <head />
      <body>
        <ul>
            <xsl:for-each select="favoriteMovies/favoriteMovie">
                <li><xsl:value-of select="." disable-output-escaping="yes" /></li>
            </xsl:for-each>
        </ul>
      </body>
  </html>
</xsl:template>

EDIT: managed it with the editor, now the code is shown as it should

EDIT2: included changes in your code

EDIT3: To whom it may concern, the very domain of the problem in question is about structuring movies information, and not html data. HTML is there for markup purposes only, imagine having, say, html title tag inside favoriteMovie, whereas the same named tag title could be a valid tag in the movies database. These title's CLEARLY have to be interpreted differently. This justifies using CDATA and then disabling output when processing.