如何使用 XSLT v1.0 而不是使用 XSLT v2.0 正则表达式插入文本?

Posted

技术标签:

【中文标题】如何使用 XSLT v1.0 而不是使用 XSLT v2.0 正则表达式插入文本?【英文标题】:How to insert text with XSLT v1.0 instead of using an XSLT v2.0 regex? 【发布时间】:2011-04-21 05:05:11 【问题描述】:

我有一个 xml 文件,其中描述了(除其他外)元素,其属性值描述了完全限定的 java 类名。我正在尝试编写一个 XSLT 转换来修改此文件中描述的类名,这样(例如)com.example.MyClass 的出现将变为com.example.MockMyClass

在原始文件片段的上下文中,这又是一个示例:

<event type="node-enter">
  <action name="MyActionName" class="com.example.MyClass">
    <bodyTemplate>
      templates/MyTemplate.vm
    </bodyTemplate>
  </action>
</event>

我希望结果是:

<event type="node-enter">
  <action name="MyActionName" class="com.example.MockMyClass">
    <bodyTemplate>
      templates/MyTemplate.vm
    </bodyTemplate>
  </action>
</event>

我正在使用 Java JAXP API 进行此转换,并编写了一个可爱的 XSLT 2.0 兼容正则表达式例程以获得我想要的结果,只是发现 Java 5 不支持正则表达式所需的 XSLT 2.0支持。

所以我的问题是,使用古老的 JAXP XSLT 1.0 API 实现这一目标的最佳方法是什么?也就是说,不使用正则表达式。我寻找了类似的问题,但是反向引用正则表达式组的要求似乎使这变得棘手。 This question 是一个开始,但我需要在匹配的字符串中插入文本,而不仅仅是替换

作为参考,这是我的正则表达式 (XSLT 2.0) 尝试:

<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
  <xsl:template match='/'>
    <xsl:analyze-string select='action/@class' regex='([A-Za-z0-9]+[$\.])+([A-Za-z0-9]+)'>
      <xsl:matching-substring>
        <xsl:value-of select='regex-group(1)'/>
        <xsl:text>Mock</xsl:text>
        <xsl:value-of select='regex-group(2)'/>
      </xsl:matching-substring>
      <xsl:non-matching-substring>
        <xsl:value-of select='.'/>
      </xsl:non-matching-substring>
    </xsl:analyze-string>
  </xsl:template>
</xsl:stylesheet>

【问题讨论】:

@Ryan,你的正则表达式中的$ 是干什么用的?如果它应该匹配行尾或字符串尾,那会不会失败,因为它必须始终跟在字母数字后面?如果是这样,那似乎是多余的。但也许我理解错了。 @LarsH $ 用于满足内部类的需求,例如com.example.MyClass$Inner。但我现在看到,这个正则表达式也可以匹配像com$example.MyClass 这样的字符串,所以如果我要继续使用正则表达式解决方案,我需要修复这个错误。 @Ryan Bennetts:你需要用.$ 标记化吗?或者只是. @Ryan 好的,谢谢。我忘记了 [] 中的 $ 是文字。内部类使用$ 表示法。 Friends don't let friends parse XML with regular expressions. 【参考方案1】:

下面的呢?

<xsl:template name="classname">
    <xsl:param name="class"/>
    <xsl:choose>
        <xsl:when test="contains($class,'.')">
            <xsl:value-of select="concat(substring-before($class,'.'),'.')"/>
            <xsl:call-template name="classname">
                <xsl:with-param name="class"
                                    select="substring-after($class,'.')"/>
            </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="concat('Mock',$class)"/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

这需要一个类名作为输入参数,并在最后的“.”之后添加“Mock”。例如,您可以调用它,

 <xsl:call-template name="classname">
     <xsl:with-param name="class" select="@class"/>
 </xsl:call-template>

(我刚刚在 Firefox 中快速尝试了一下,您可能会发现需要整理一些空白区域。)

【讨论】:

+1。请注意,尽管对于类名不包含 . 的基本情况,它给出了不同的结果,但是这种情况在实践中可能不会发生。 +1 - 您可以通过将文字文本节点包装在&lt;xsl:text&gt; 中来避免一些空白问题,而不是在模板内“裸露”。这样您就可以格式化您的代码,而不必担心模板内的回车和其他空格会潜入输出。只有在非空白字符之前或之后的空白字符才被视为重要字符。包裹在 &lt;xsl:text&gt; 中将用于输出的文本内容与格式化空格分开。 谢谢,我不知道。 @Matthw Wilson:+1 好答案。 这不处理内部类,例如com.example.MyClass$Inner,但这对我来说在这个阶段并不那么重要。我在找到正确的上下文来调用您的模板时遇到了一些麻烦,但最终通过匹配"action/@class" 并使用&lt;xsl:with-param name="class" select="."/&gt; 调用模板来解决这个问题,谢谢!【参考方案2】:

以下内容似乎很长,但它使用了现成的部分(strRev 模板由 FXSL 提供,无需重新编写)。此外,将近一半的代码是身份模板并将参数传递给&lt;xsl:call-template&gt;。这在 XSLT 2.0 中被大大缩短了。

当我们准备好像strRev 模板/reverse() 函数这样的较小部分/函数时,这个解决方案不需要编写冗长且容易出错的自制递归代码。

基本思想是字符串中的最后一个'.'字符是反转字符串中的第一个'.'字符

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

 <xsl:param name="pPrepend" select="'Mock'"/>
 <xsl:variable name="vRevPrepend">
  <xsl:call-template name="strRev">
   <xsl:with-param name="pText" select="$pPrepend"/>
  </xsl:call-template>
 </xsl:variable>


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

 <xsl:template match="action/@class">
   <xsl:variable name="vRevText">
    <xsl:call-template name="strRev"/>
   </xsl:variable>

   <xsl:variable name="vRevNew" select=
   "concat(substring-before($vRevText,'.'), $vRevPrepend,
           '.', substring-after($vRevText,'.'))"/>

   <xsl:variable name="vNewText">
     <xsl:call-template name="strRev">
      <xsl:with-param name="pText" select="$vRevNew"/>
     </xsl:call-template>
   </xsl:variable>

  <xsl:attribute name="class">
   <xsl:value-of select="$vNewText"/>
  </xsl:attribute>
 </xsl:template>

 <xsl:template name="strRev">
  <xsl:param name="pText" select="."/>

  <xsl:if test="string-length($pText)">
   <xsl:call-template name="strRev">
    <xsl:with-param name="pText" select="substring($pText,2)"/>
   </xsl:call-template>
   <xsl:value-of select="substring($pText,1,1)"/>
  </xsl:if>
 </xsl:template>
</xsl:stylesheet>

当此转换应用于提供的 XML 文档时

<event type="node-enter">
  <action name="MyActionName" class="com.example.MyClass">
    <bodyTemplate>
      templates/MyTemplate.vm
    </bodyTemplate>
  </action>
</event>

产生想要的正确结果

<event type="node-enter">
    <action name="MyActionName" class="com.example.MockMyClass">
        <bodyTemplate>
          templates/MyTemplate.vm
        </bodyTemplate>
    </action>
</event>

二。 XSLT 2.0 解决方案

完全相同的算法,但在 XSLT 2.0 中确实很短

<xsl:stylesheet version="2.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:xs="http://www.w3.org/2001/XMLSchema"
 xmlns:my="my:my">
 <xsl:output omit-xml-declaration="yes"/>

 <xsl:param name="pPrepend" select="'Mock'"/>

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

    <xsl:template match="action/@class">
     <xsl:attribute name="class" select=
     "my:strRev(concat(substring-before(my:strRev(.),'.'),
                       my:strRev($pPrepend),'.',
                       substring-after(my:strRev(.),'.')
                       )
                )
     "/>
    </xsl:template>

    <xsl:function name="my:strRev" as="xs:string">
      <xsl:param name="pText" as="xs:string"/>

      <xsl:sequence select=
       "codepoints-to-string(reverse(string-to-codepoints($pText)))
       "/>
    </xsl:function>
</xsl:stylesheet>

【讨论】:

我不能说我已经完全掌握了你的代码,但看起来你已经硬编码了“MockMyClass”作为转换后的类名。但是,我想要做的是 prepend 在类名中添加“Mock”这个词,以便 - 例如 - com.example.ExampleClass 变为 com.example.MockExampleClasscom.example.AnotherExampleClass 变为 com.example.MockAnotherExampleClass 等。 @Ryan-Bennetts:当然,这是最简单的事情——只需等待 2 分钟。 :) 感谢 Dimitre,您的代码运行良好,但我选择了 Matthew Wilson 的解决方案,因为它更简洁。 @Ryan-Bennetts:作为消费者,这对你来说应该都是一样的,但如果你是开发人员,我的解决方案是一种可以为你节省大量时间和错误的设计模式-- 您只需使用现成的函数/模板,无需编写自己的递归代码。 String reverse 只是我的路径问题的票/content/art/VO1113VIEW05.01.tif 我需要去除.tif 但不是.05.tif

以上是关于如何使用 XSLT v1.0 而不是使用 XSLT v2.0 正则表达式插入文本?的主要内容,如果未能解决你的问题,请参考以下文章

XSLT:如何删除同义名称空间

如何使用 XSLT 制作 xml 节点的子节点的精确副本?

如何使用 XSLT 测试图像文件是不是存在?

如何使用 xslt 将父节点命名空间复制到子元素?

自动生成XSLT - 通用/默认XSLT

如何使用 xslt 更改单个标签?