How to output
    and
  • elements from XML to X

2019-08-23 11:38发布

I have several unordered lists in an XML file that I need to convert to HTML with an XSL file. These lists inside my XML look like:

<Food>
   <FoodItem ID="1">
      <Content><![CDATA[<ul><li>Apples</li><li>Pears</li><li>Oranges</li><ul>]]></Content>
   </FoodItem>
</Food>

I would like to grab the unordered lists from the XML file and output them in my XSL file like so that I can stlye them with CSS. The result should be like this:

<div class="food">
    <ul class="fooditems">
        <li class="fooditem">Apples</li>
        <li class="fooditem">Pears</li>
        <li class="fooditem">Oranges</li>
    </ul>
</div>

However, <xsl:value-of select="key('fooditem', 1)/Content only gave me a string of <ul><li>Apples</li><li>Pears</li><li>Oranges</li></ul>.

The key is defined as <xsl:key name="fooditem" match="FoodItem" use="@ID" />.

How can I get the individual list tags including their content out of the <![CDATA[]]> tag?

标签: xml xslt
2条回答
SAY GOODBYE
2楼-- · 2019-08-23 12:07

How can I get the individual list tags including their content out of the tag?

For an XSLT-1.0 solution, you have to use a two step approach:

  1. Get the items out of the CDATA section.
  2. Apply the templates you want to apply.

One problem is that the CDATA sample is not well-formed XML. So you have to change your input XML (changing the last <ul> to </ul>) to the following:

<Food>
   <FoodItem ID="1">
      <Content><![CDATA[<ul><li>Apples</li><li>Pears</li><li>Oranges</li></ul>]]></Content>
   </FoodItem>
</Food>

For the first step you can use this (assuming that your XSLT processor does support the disable-output-escaping attribute):

<?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" version="1.0" encoding="UTF-8" indent="yes"/>

    <!-- Identity template -->
    <xsl:template match="node()[not(self::text())]|@*">
        <xsl:copy>
            <xsl:apply-templates select="node()|@*"/>
        </xsl:copy>
    </xsl:template>    

    <xsl:template match="text()">            
        <xsl:value-of select="." disable-output-escaping="yes" />
    </xsl:template>

</xsl:stylesheet>

Now you can apply your XSLT template to the output of the above stylesheet.

The result of this transformation should be:

<?xml version="1.0" encoding="UTF-8"?>  
<Food>
    <FoodItem ID="1">
        <Content>
            <ul>
                <li>Apples</li>
                <li>Pears</li>
                <li>Oranges</li>
            <ul>
        </Content>
    </FoodItem>
</Food>

Now, the second step is applying a different XSLT-1.0 stylesheet to this output.
So apply this second XSLT-1.0 stylesheet:

<?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" version="1.0" encoding="UTF-8" indent="yes"/>

    <xsl:template match="/Food/FoodItem/Content/ul">    
        <div class="food">
            <ul class="fooditems">
                <xsl:apply-templates select="*" />
            </ul>
        </div>
    </xsl:template>

    <xsl:template match="li"> 
        <li class="fooditem"><xsl:value-of select="." /></li>
    </xsl:template>

</xsl:stylesheet>

The result of this second step is:

<div class="food">
    <ul class="fooditems">
        <li class="fooditem">Apples</li>
        <li class="fooditem">Pears</li>
        <li class="fooditem">Oranges</li>
    </ul>
</div>

which is as desired.

SOLVED!


With newer versions of XSLT processors (3.0 and above) you can (possibly) do both steps in one stylesheet.

查看更多
劫难
3楼-- · 2019-08-23 12:10

First of all, the ul/li elements are not as nodes in your input XML but rather as escaped markup in a CDATA section. So you would need to parse that markup before being able to transform nodes. With XSLT 3 (as supported by Saxon 9.8 and later or AltovaXML 2017 and later) you can use the parse-xml or parse-xml-fragment XPath 3 function to parse a string with an XML document or an XML fragment into nodes.

However, at least in your question's sample, the escaped markup is not well-formed XML as it doesn't have a properly closed ul element, it is <ul><li>Apples</li><li>Pears</li><li>Oranges</li><ul> where it should be <ul><li>Apples</li><li>Pears</li><li>Oranges</li></ul> to be parseable as XML.

So depending on the real sample, if that is well-formed, you can use

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="#all"
    version="3.0">

  <xsl:mode on-no-match="shallow-copy"/>

  <xsl:output method="html" indent="yes" html-version="5"/>

  <xsl:template match="FoodItem">
      <div class="food">
          <xsl:apply-templates select="parse-xml-fragment(Content)/node()" mode="food"/>
      </div>
  </xsl:template>

  <xsl:mode name="food" on-no-match="shallow-copy"/>

  <xsl:template match="ul" mode="food">
      <ul class="fooditems">
          <xsl:apply-templates select="@* , node()" mode="#current"/>
      </ul>
  </xsl:template> 

  <xsl:template match="li" mode="food">
      <li class="fooditem">
          <xsl:apply-templates select="@* , node()" mode="#current"/>
      </li>
  </xsl:template>

</xsl:stylesheet>

as shown in https://xsltfiddle.liberty-development.net/3NJ38ZH/1 and get your wanted output

   <div class="food">
      <ul class="fooditems">
         <li class="fooditem">Apples</li>
         <li class="fooditem">Pears</li>
         <li class="fooditem">Oranges</li>
      </ul>
   </div>
查看更多
登录 后发表回答