使用XSLT转换XML

3

我有以下的xml:

<fo:block font-weight="bold" font-style="italic">Some Text Here</fo:block>

我需要使用xsl将其转换为以下格式:
{\b\iSome Text Here\i0\b0\par}

到目前为止,我已经成功使用以下方法选择了块级元素:

<xsl:template match="fo:block">
<xsl:text>{</xsl:text>
    <xsl:apply-templates />
<xsl:text>\par}</xsl:text></xsl:template>

我正在输出:{这里有一些文本\par}

我在处理属性和使用xsl插入时遇到了困难,有人能给我一个选择这些属性并得到所需结果的示例吗?


好问题,+1。请查看我的答案,其中包含完整的解决方案,易于通用化,因此可以轻松添加新属性的处理。 - Dimitre Novatchev
或者,您可以看看我的代码,它更短,而且根本没有条件指令。 - Flynn1179
@Dusty Roberts:我已经添加了另一种解决方案,它能够保留属性的词汇顺序。@Flynn的解决方案确实很优雅,但它会破坏这个顺序。 - Dimitre Novatchev
考虑到在这种情况下没有必要保留它,那么批评是不合理的,特别是因为我的解决方案非常容易扩展以维护该顺序(如果需要)。 - Flynn1179
@Flynn1179:感谢您注意到这个问题——现在已经修复了。请不要低估您的解决方案的价值,因为它基于一个好的想法,但也请接受有人可能对问题和什么是真正好的解决方案有不同的理解。 - Dimitre Novatchev
显示剩余2条评论
3个回答

3

有一种相当简单的通用方法,可以使用模板模式和 <xsl:sort> 指令来完成。

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

<xsl:template match="fo:block">
  <xsl:text>{</xsl:text>
  <xsl:apply-templates select="@*" mode="prefix" />
  <xsl:apply-templates select="node()" />
  <xsl:apply-templates select="@*" mode="suffix">
    <xsl:sort order="descending"/>
  </xsl:apply-templates>
  <xsl:text>\par}</xsl:text>
</xsl:template>

<xsl:template match="@font-weight['bold']" mode="prefix">\b</xsl:template>
<xsl:template match="@font-style['italic']" mode="prefix">\i</xsl:template>

<xsl:template match="@font-weight['bold']" mode="suffix">\b0</xsl:template>
<xsl:template match="@font-style['italic']" mode="suffix">\i0</xsl:template>

</xsl:stylesheet>
<xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>

在'suffix'模式下,<xsl:sort order="descending" />会反转属性的顺序。

严格来说,在主模板中间的select="node()"是多余的,但它使阅读更清晰,只有节点被处理,而不是属性。

你可以通过将现有的'suffix'模式模板替换为以下内容,使添加新属性变得更容易:

<xsl:template match="@*" mode="suffix">
  <xsl:apply-templates select="." mode="prefix" />
  <xsl:text>0</xsl:text>
</xsl:template>

这只是使用前缀的模板,并在末尾添加额外的0。如果某些属性不能像通用属性一样处理,您可以随时覆盖它。


每个属性都必须生成输出两次,这似乎并不过分。 - Flynn1179
@flynn1179:另一个问题(可能与这个具体的问题无关)是输出顺序与属性的词法顺序没有关系。是的,我们都知道属性顺序只是一个词法属性,但我使用的所有9个XSLT处理器都会根据它们的词法顺序产生推送式处理属性的结果。在某些情况下,保留此顺序对于人类读者很重要。 - Dimitre Novatchev
@Dimitre,很明显这与此案无关。如果词汇顺序很重要,那么很容易添加<xsl:sort select="name()" />和相同的选择属性到降序排序来实现这一点。通常来说,如果排序很重要,那么最好将其明确编码以防止跨平台不一致。 - Flynn1179
@Flynn1179:实际上,我基于你的解决方案提出了一种新的有序解决方案,但是大大简化了它——模板更少(仅3个而不是5个),也没有任何模式。而且没有显式的<xsl:apply-templates>顺序,因此你的建议并不是必要的。 - Dimitre Novatchev

1
<xsl:template match="fo:block">
    <xsl:text>{</xsl:text>
        <xsl:if test="@font-weight = 'bold'">\b</xsl:if>
        <xsl:if test="@font-style = 'italic'">\i</xsl:if>

        <xsl:apply-templates />

        <xsl:if test="@font-style = 'italic'">\i0</xsl:if>
        <xsl:if test="@font-weight = 'bold'">\b0</xsl:if>
    <xsl:text>\par}</xsl:text>
</xsl:template/>

另外,您可以查看w3schools.com以获取有关XSLT的更多阅读材料。


这很好,谢谢,但是有没有可能让if语句更通用一些,比如通过循环属性的方式? - stoic

1

这种转换更加通用和可扩展

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

 <xsl:template match="/*">
  <xsl:variable name="vAttributesResult">
   <xsl:call-template name="processAttributes"/>
  </xsl:variable>

  <xsl:value-of select=
   "concat(substring-before($vAttributesResult, ' '),
           .,
           substring-after($vAttributesResult, ' ')
           )
   "/>
 </xsl:template>

 <xsl:template name="processAttributes">
  <xsl:param name="pattrList" select="@*"/>
  <xsl:param name="pResult" select="' '"/>

  <xsl:choose>
      <xsl:when test="not($pattrList)">
       <xsl:value-of select="$pResult"/>
      </xsl:when>
      <xsl:otherwise>
       <xsl:variable name="vthisResult">
        <xsl:apply-templates select="$pattrList[1]">
         <xsl:with-param name="pResult" select="$pResult"/>
        </xsl:apply-templates>
       </xsl:variable>

       <xsl:call-template name="processAttributes">
        <xsl:with-param name="pattrList" select="$pattrList[position()>1]"/>
        <xsl:with-param name="pResult" select="$vthisResult"/>
       </xsl:call-template>
      </xsl:otherwise>
  </xsl:choose>
 </xsl:template>

 <xsl:template match="@font-weight[.='bold']">
  <xsl:param name="pResult"/>
  <xsl:value-of select="concat('\b', $pResult, '\b0')"/>
 </xsl:template>

 <xsl:template match="@font-style[.='italic']">
  <xsl:param name="pResult"/>
  <xsl:value-of select="concat('\i', $pResult, '\i0')"/>
 </xsl:template>
</xsl:stylesheet>

当应用于提供的XML文档(已更正为格式良好)时:

<fo:block font-weight="bold" font-style="italic"
xmlns:fo="some:fo">Some Text Here</fo:block>

期望的结果已经生成

\i\bSome Text Here\b0\i0

请注意

  1. 您可以轻松地为任意数量的新属性/值组合添加处理方式 -- 只需为新属性添加一个新模板即可。

  2. 如果顺序很重要,请使用这些模板来处理属性:

--

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

    <xsl:template match="/*">
        <xsl:text>{</xsl:text>
        <xsl:apply-templates select="@*">
         <xsl:with-param name="pSuff" select="''"/>
         <xsl:sort select="position()"/>
        </xsl:apply-templates>

        <xsl:apply-templates select="node()" />

        <xsl:apply-templates select="@*">
         <xsl:with-param name="pSuff" select="'0'"/>
         <xsl:sort select="position()" order="descending"/>
        </xsl:apply-templates>

        <xsl:text>\par}</xsl:text>
    </xsl:template>

    <xsl:template match="@font-weight['bold']">
      <xsl:param name="pSuff"/>
      <xsl:value-of select="concat('\b',$pSuff)"/>
    </xsl:template>

    <xsl:template match="@font-style['italic']">
      <xsl:param name="pSuff"/>
      <xsl:value-of select="concat('\i',$pSuff)"/>
    </xsl:template>
</xsl:stylesheet>

这个转换借鉴了 @Flynn1169 的答案思路,并显著简化了它(只有 3 个模板而不是 t,也没有模式),最重要的是,按属性的词法顺序呈现结果。

在这种情况下,结果匹配属性的词法顺序,而不是它们排序后的名称!:

如果我们有这个 XML 文档

    <fo:block font-style="italic" font-weight="bold" 
xmlns:fo="some:fo">Some Text Here</fo:block>

现在的结果是:

{\i\bSome Text Here\b0\i0\par}

备注:虽然在XPath数据模型中没有“属性顺序”这样的概念,但我正在使用的所有推送式模式的XSLT处理器(超过9个)都会按照它们的词汇顺序处理属性。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接