使用XSLT模拟段落中的列表,而不会丢失周围的段落

时间:2014-06-12 15:50:56

标签: html xml xslt

我需要使用XSLT来转换以下格式的XML(我无法控制XML本身的格式,因此我无法告诉您为什么列表的格式是这样的):

<p id="p-0005" num="0004">Here is a statement and these are my reasons:
    <ul id="ul0001" list-style="none">
        <li id="ul0001-0001" num="0000">
            <ul id="ul0002" list-style="none">
                <li id="ul0002-0001" num="0005">a. Reason number 1.</li>
                <li id="ul0002-0002" num="0006">b. Reason number 2.</li>
                <li id="ul0002-0003" num="0007">c. Reason number 3.</li>
            </ul>
        </li>
    </ul>
</p>

我只有几个小时的XSLT经验,到目前为止我可以提取整个段落列表和所有:

<xsl:template match="p">
    <p>
        <xsl:value-of select="."/>
    </p>
</xsl:template>

或者我只能提取列表,而不是前面的句子:

<xsl:template match="p">
    <ul style="list-style: none;">
        <xsl:for-each select="./ul/li/ul/li">
            <li>
                <xsl:value-of select="."/>
            </li>
        </xsl:for-each>
    </ul>
</xsl:template> 

我想要做的是以HTML等效于:

<p> Here is a statement I am making and these are my reasons
    <ul>
        <li> Reason number 1. </li>
        <li> Reason number 2. </li>
        <li> Reason number 3. </li>
    </ul>
</p>

我已经弄乱了<xsl:apply-templates/>元素,但无论我尝试什么,我最终都会丢失或重复信息。非常感谢任何帮助!

2 个答案:

答案 0 :(得分:2)

进行您似乎想要的转换的最简单方法是为每个元素类型编写一个模板,并使用apply-templates来处理控制流:

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

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

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

这里有几点值得注意。首先,所有模板首先生成它们匹配的相同类型的元素 - 这非常接近于身份转换,因此许多XSLT程序员将以不同方式和更紧凑的方式处理这种特定情况。但更紧凑的解决方案更难理解,并没有清楚地说明这一基本习语。

其次,我们丢失了所有属性,因为默认情况下<xsl:apply-templates/>不会选择属性。

第三,这只是用相同的结构重现输入,而你显然想要丢失最外面的ul元素,其中只包含一个li和单个li它。您需要确定为什么要丢失这些元素,以便在代码中正确表达这些条件。假设规则是“只要外部ul只包含一个只包含一个ul的li,就会丢失外部ul和li”,那么你可以通过在样式表中添加一个额外的模板来处理这个问题:

<xsl:template match="ul[count(*) = 1
                        and ./li 
                        and count(li/*) = 1 
                        and ./li/ul]">
  <xsl:apply-templates select="li/ul"/>
</xsl:template>

这不能处理剥离'a。','b。'和'c'的工作。关闭内部列表项;如果你真的需要的话,我会把它作为练习留给你。

如果您在学习如何正确使用apply-templates方面遇到困难,那么除非您了解这些模板的工作方式和原因,否则您可能会比这更糟糕。在您了解如何使用apply-templates之前,您将永远不会真正熟悉XSLT。


附录:你问“你怎么能避免使用xsl:value-of元素,同时仍然将其值输入输出?”

XML输入中的字符数据在XSLT数据模型中表示为文本节点;文本节点像任何其他节点一样处理。如果你愿意,你可以写一个模板:

<xsl:template match="text()">
  <text-node>There was a text node here.</text-node>
</xsl:template>

或者,对于这个特殊情况更有用:

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

如果将最后一个模板添加到样式表中,您会发现它对行为没有影响。为什么?因为刚才给出的模板与默认模板基本相同,默认模板为任何与某些用户提供的模板不匹配的文本节点触发。 (因此,通常不必为文本节点编写模板 - 默认行为通常很好。)

在元素节点上使用value-of元素的缺点是它为您提供了元素的字符串值,(如您的示例所示)通常涉及丢失元素中任何内部标记的所有结构。出于这个原因,许多XSLT程序员几乎从未在编写XML到XML转换时使用它。当然,它确实用于处理文本节点。

答案 1 :(得分:0)

  

我已经能够只提取列表,没有句子   在它之前:

尝试在<xsl:value-of select="text()"/>元素之前添加ul

<xsl:template match="p">
    <xsl:value-of select="text()"/> <!-- here  -->
    <ul style="list-style: none;">
        <!-- <xsl:value-of select="text()"/>  NOT here  --> 
        <xsl:for-each select="./ul/li/ul/li">
            <li>
                <xsl:value-of select="."/>
            </li>
        </xsl:for-each>
    </ul>
</xsl:template> 

我还建议您了解身份转换模板,因为这可能是一个更方便的起点(当然在其他情况下)。