如何使用 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 - 您可以通过将文字文本节点包装在<xsl:text>
中来避免一些空白问题,而不是在模板内“裸露”。这样您就可以格式化您的代码,而不必担心模板内的回车和其他空格会潜入输出。只有在非空白字符之前或之后的空白字符才被视为重要字符。包裹在 <xsl:text>
中将用于输出的文本内容与格式化空格分开。
谢谢,我不知道。
@Matthw Wilson:+1 好答案。
这不处理内部类,例如com.example.MyClass$Inner
,但这对我来说在这个阶段并不那么重要。我在找到正确的上下文来调用您的模板时遇到了一些麻烦,但最终通过匹配"action/@class"
并使用<xsl:with-param name="class" select="."/>
调用模板来解决这个问题,谢谢!【参考方案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”作为转换后的类名。但是,我想要做的是 prepend 在类名中添加“Mock”这个词,以便 - 例如 -com.example.ExampleClass
变为 com.example.MockExampleClass
或 com.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 正则表达式插入文本?的主要内容,如果未能解决你的问题,请参考以下文章