Apply XSLT template respecting the order in the XM

2019-09-06 11:59发布

Hi I have an xml file which I have to convert to a html-page. The structure of this file is (simplified) like:

<top>
<element>
    <option id="SectionA">      
        <content id="ContentA1"></content>
        <content id="ContentA2"></content>
        <option id="ContentA3">
            <content id="ContentA31"></content>
            <content id="ContentA32"></content>
            <option id="ContentA33"></option>
        </option>
        <content id="ContentA4"></content>
    </option>
    <option id="SectionB">
        <content id="ContentA1"></content>
        <content id="ContentA2"></content>
    </option>       
    <option id="SectionC">
        <content id="ContentA1"></content>
        <content id="ContentA2"></content>      
    </option>       
</element>

My XSL file looks like (simplified):

<?xml version="1.0" encoding="utf-8"?>

<xsl:template match="/">    
    <!-- Output html -->
    <xsl:apply-templates select="top/elements"/>
</xsl:template>  

<xsl:template match="top/elements">
    <xsl:apply-templates select="option" />
</xsl:template>


<xsl:template match="option">       
    <!-- Output html -->
    <xsl:if test="content">             
        <xsl:apply-templates select="content"/>
    </xsl:if>   
    <xsl:if test="option">  
        <xsl:apply-templates select="option"/>
    </xsl:if>                       
</xsl:template>

<xsl:template match="content">
    <!-- Output html -->
</xsl:template>

Conversions works, but now I have to force the output in such a way, that the order of the elements is respected. As far as I know, using the templates forces the XSL to iterate through all elements which are defined for it. So first <xsl:apply-templates select="content" /> in the option-template will output all content-elements and after that it will output all option-elements (which are on a sublevel). The problem is, I have to maintain the same order as in the XML-document. So the output for sectionA should be:

  • ContentA1
  • ContentA2
  • OptionA3
  • ContentA4

Instead of the output of my solution:

  • ContentA1
  • ContentA2
  • ContentA4
  • OptionA3

So it will first handle all Content-elements and than the option-elements. How can I force the order which is in the xml for the output?

标签: xml xslt
2条回答
乱世女痞
2楼-- · 2019-09-06 12:29

Your tests are unnecessary: if a node does not exist, then a template matching it will not be executed. Instead of:

<xsl:template match="option">       
    <!-- Output html -->
    <xsl:if test="content">             
        <xsl:apply-templates select="content"/>
    </xsl:if>   
    <xsl:if test="option">  
        <xsl:apply-templates select="option"/>
    </xsl:if>                       
</xsl:template>

you can do simply:

<xsl:template match="option">       
    <!-- Output html -->
    <xsl:apply-templates select="content | option"/>        
</xsl:template>
查看更多
姐就是有狂的资本
3楼-- · 2019-09-06 12:50

The XSLT processor works through your input XML in document order by default.

All you need to do is use <xsl:apply-templates /> and get out of the way.

To show what I mean, the following simple transformation maps <element> to <section>, <option> to <div> and <content> to <p>:

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

    <xsl:template match="/">
        <html>
            <xsl:apply-templates select="top/element" />
        </html>
    </xsl:template>

    <xsl:template match="element">
        <section>
            <xsl:apply-templates />
        </section>
    </xsl:template>

    <xsl:template match="option">
        <div>
            <span><xsl:value-of select="@id" /></span>
            <xsl:apply-templates />
        </div>
    </xsl:template>

    <xsl:template match="content">
        <p>
            <span><xsl:value-of select="@id" /></span>
            <xsl:apply-templates />
        </p>
    </xsl:template>
</xsl:stylesheet>

which results in

<html>
   <section>
      <div>
         <span>SectionA</span>
         <p>
            <span>ContentA1</span>
         </p>
         <p>
            <span>ContentA2</span>
         </p>
         <div>
            <span>ContentA3</span>
            <p>
               <span>ContentA31</span>
            </p>
            <p>
               <span>ContentA32</span>
            </p>
            <div>
               <span>ContentA33</span>
            </div>
         </div>
         <p>
            <span>ContentA4</span>
         </p>
      </div>
      <div>
         <span>SectionB</span>
         <p>
            <span>ContentA1</span>
         </p>
         <p>
            <span>ContentA2</span>
         </p>
      </div>
      <div>
         <span>SectionC</span>
         <p>
            <span>ContentA1</span>
         </p>
         <p>
            <span>ContentA2</span>
         </p>
      </div>
   </section>
</html>

A construct like this:

<xsl:if test="content">             
    <xsl:apply-templates select="content"/>
</xsl:if> 

is not really necessary. When there are no content nodes, <xsl:apply-templates> would not have anything to work on, so this would be equivalent:

<xsl:apply-templates select="content"/>

and since you want all the children processed in their original order, it's easiest to do:

<xsl:apply-templates />

or, if you want to be more specific, you can use a union:

<xsl:apply-templates select="content|option"/>

This also would maintain input order.

查看更多
登录 后发表回答