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

4

我有一个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>

I want the result to be:

<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,最佳方法是什么?也就是说,不使用正则表达式。我寻找类似的问题,但是对于反向引用正则表达式组的要求似乎使得这个问题棘手起来。这个问题是一个开始,但是我需要在匹配字符串中插入文本,而不仅仅是替换
供参考,这是我的正则表达式(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
@LarsH $符号的作用是为了匹配内部类,例如 com.example.MyClass$Inner。但我现在发现这个正则表达式也会匹配像 com$example.MyClass 这样的字符串,所以如果我要继续使用正则表达式解决方案,我需要修复这个错误。 - Ryan Bennetts
1
朋友们不要用正则表达式解析XML。 - Ether
@Ryan-Bennetts,@LarsH,@Alejandro,@Matthew-Wilson:请看我的回答,代表了一个懒惰的程序员,只使用现成的部件并节省时间。无论是在XSLT 2.0还是XSLT 1.0中。 :) - Dimitre Novatchev
@Ryan-Bennetts:好问题,点赞。看看我的解决方案,这是一种“懒惰”的替代方案——适用于XSLT 1.0和XSLT 2.0。 :) - Dimitre Novatchev
显示剩余3条评论
2个回答

3
以下内容怎么样?
<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. 请注意,当类名不包含 . 时,它会给出不同的结果。然而,在实践中可能不会出现这种情况。 - LarsH
1
+1 - 通过将文字节点包装在<xsl:text>中,而不是在模板内“裸露”地悬挂,您可以避免一些空格问题。这样,您可以格式化代码,而不必担心回车和其他空格字符进入模板并混入输出中。只有在前面或后面跟随非空格字符时,空格字符才被视为重要。在<xsl:text>中包装文本内容可将其与格式化空格分开,以便用于输出。 - Mads Hansen
@Matthw Wilson:+1 很好的回答。 - user357812
这个程序无法处理内部类,例如 com.example.MyClass$Inner,但在目前阶段对我来说并不那么重要。我曾经遇到过一些问题,找不到正确的上下文来调用您的模板,但最终通过匹配 "action/@class" 并使用 <xsl:with-param name="class" select="."/> 调用模板解决了问题。谢谢! - Ryan Bennetts

2
以下内容看起来很长,但它使用了现成的部分(strRev 模板由 FXSL 提供,无需重新编写)。另外,近一半的代码是身份模板,并将参数传递给 <xsl:call-template>。在 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”作为转换后的类名。然而,我的目标是在类名前__添加__单词“Mock”,以便例如com.example.ExampleClass变成com.example.MockExampleClass或者com.example.AnotherExampleClass变成com.example.MockAnotherExampleClass等。 - Ryan Bennetts
@Ryan-Bennetts:当然,这是最简单的事情--只需要等待2分钟。 :) - Dimitre Novatchev
谢谢Dimitre,你的代码非常好用,但是我选择了Matthew Wilson的解决方案,因为它更加简洁。 - Ryan Bennetts
@Ryan-Bennetts:作为消费者,这对你来说应该都是一样的,但如果你是开发人员,我的解决方案是一种设计模式,可以节省你大量的时间和错误——你只需使用现成的函数/模板,而不必编写自己的递归。 - Dimitre Novatchev
1
字符串反转正是我解决问题的好助手,我的问题是针对路径“/content/art/VO1113VIEW05.01.tif”,我需要去掉“.tif”,但不能去掉“.05.tif”。 - Michael Shopsin

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