XSL:用XML标签替换特定字符

6

这个问题有些棘手,我卡在这里有一段时间了。我的目标是将方括号 '['(例如用于按钮、链接等)替换为<code>标签,并将 ']' 替换为 </code>。

<section>
    <title>Buttons</title>
    <orderedlist>
        <listitem>
            <para>Clicking on [Save] will attempt to save changes, then it navigates to <xref linkend="saved" xrefstyle="select: title"/>.</para>
        </listitem>
        <listitem>
            <para>Clicking on [Cancel] navigates to <xref linkend="noSave" xrefstyle="select: title"/>.</para>
        </listitem>
    </orderedlist>
</section>

To:

<section>
    <title>Buttons</title>
    <orderedlist>
        <listitem>
            <para>Clicking on <uicontrol>Save</uicontrol> will attempt to save changes, then it navigates to <xref linkend="saved" xrefstyle="select: title"/>.</para>
        </listitem>
        <listitem>
            <para>Clicking on <uicontrol>Cancel</uicontrol> navigates to <xref linkend="noSave" xrefstyle="select: title"/>.</para>
        </listitem>
    </orderedlist>
</section>

而且 '[' ']' 不一定总是在 section.listitem.para 中。

编辑:当括号中出现特定单词时,我只需要 [] 替换。


好问题,+1。请看我的答案,这里有一个更安全和更高效的解决方案。 - Dimitre Novatchev
看了@Dimitre的回答后,我认为更好的方法是:在括号中固定文本以进行搜索。这使问题变得不那么普遍,因此您不必担心嵌套或孤立的括号。请注意,对于嵌套的括号,您将需要解析! - user357812
3个回答

3

对于不嵌套的 uicontrol,不需要解析平衡括号和非平衡括号。

这个样式表:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:template match="node()|@*">
  <xsl:copy>
   <xsl:apply-templates select="node()|@*"/>
  </xsl:copy>
 </xsl:template>
 <xsl:template match="text()" name="replace" priority="1">
  <xsl:param name="pString" select="."/>
  <xsl:variable name="vMask" select="translate($pString,
                                                     translate($pString,
                                                               '[]',
                                                               ''),
                                                     '')"/>
  <xsl:choose>
   <xsl:when test="contains($vMask,'[]')">
    <xsl:call-template name="makeControl">
     <xsl:with-param name="pString" select="$pString"/>
     <xsl:with-param name="pMask"
                     select="substring-before($vMask,'[]')"/>
    </xsl:call-template>
   </xsl:when>
   <xsl:otherwise>
    <xsl:value-of select="$pString"/>
   </xsl:otherwise>
  </xsl:choose>
 </xsl:template>
 <xsl:template name="makeControl">
  <xsl:param name="pString"/>
  <xsl:param name="pMask"/>
  <xsl:choose>
   <xsl:when test="$pMask">
    <xsl:variable name="vMask" select="substring($pMask,1,1)"/>
    <xsl:value-of select="concat(
                             substring-before(
                                $pString,
                                $vMask),
                             $vMask)"/>
    <xsl:call-template name="makeControl">
     <xsl:with-param name="pString"
                     select="substring-after($pString,$vMask)"/>
     <xsl:with-param name="pMask" select="substring($pMask,2)"/>
    </xsl:call-template>
   </xsl:when>
   <xsl:otherwise>
    <xsl:value-of select="substring-before($pString,'[')"/>
    <uicontrol>
     <xsl:value-of select="substring-before(
                                             substring-after(
                                                $pString,
                                                '['),
                                             ']')"/>
    </uicontrol>
    <xsl:call-template name="replace">
     <xsl:with-param name="pString"
                                    select="substring-after($pString,']')"/>
    </xsl:call-template>
   </xsl:otherwise>
  </xsl:choose>
 </xsl:template>
</xsl:stylesheet>

输出:

<section>
 <title>Buttons</title>
 <orderedlist>
  <listitem>
   <para>Clicking on <uicontrol>Save</uicontrol> will attempt to save changes, then it navigates to <xref linkend="saved" xrefstyle="select: title"></xref>.</para>
  </listitem>
  <listitem>
   <para>Clicking on <uicontrol>Cancel</uicontrol> navigates to <xref linkend="noSave" xrefstyle="select: title"></xref>.</para>
  </listitem>
 </orderedlist>
</section>

并且使用以下输入:

<text>
This is an opening bracket [ ? [Yes] [No]
This is a closing bracket ] ? [Yes] [No]
</text>

输出:

<text>
This is an opening bracket [ ? <uicontrol>Yes</uicontrol> <uicontrol>No</uicontrol>
This is a closing bracket ] ? <uicontrol>Yes</uicontrol> <uicontrol>No</uicontrol>
</text>

注意:任何匹配\[[^\[\]]*\]的文本都将被包装在uicontrol元素中。


@Ace:这只是一个模板冲突解决的警告。现在通过显式的@priority已经消失了。另外,请注意答案是不同的。我的保留了孤立的括号。 - user357812
@Alejandro:好答案,+1。我也很想知道你对我的回答有什么看法。 - Dimitre Novatchev
@Ace:看了@Dimitre的回答,我认为更好的方法是:在括号中固定文本以进行搜索。这使问题变得不那么普遍,因此您不必担心嵌套或孤立的括号。请注意,对于嵌套括号,您将需要解析! - user357812
@Alejandro:在前一种情况下,每个“是”和“否”都有自己的uicontrol,没有“[”或“]”。 - Ace
@Ace:检查我的编辑,查找任何在括号中的文本,没有嵌套的uicontrol - user357812
显示剩余3条评论

2
你可以使用 containssubstring-beforesubstring-after 函数来查找括号,然后插入所需的元素而不是括号。
编辑 - 应该可以这样做:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
    <xsl:output method="xml" indent="yes"/>

    <xsl:template name="insertElements">
        <xsl:param name="text" select="." />
        <xsl:choose>
            <xsl:when test="contains($text, '[')">
                <xsl:value-of select="substring-before($text, '[')"/>
                <xsl:variable name="after" select="substring-after($text, '[')" />
                <uicontrol>
                    <xsl:value-of select="substring-before($after, ']')"/>
                </uicontrol>
                <xsl:call-template name="insertElements">
                    <xsl:with-param name="text" select="substring-after($after, ']')" />
                </xsl:call-template>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="$text"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>

    <xsl:template match="@* | node()">
        <xsl:copy>
            <xsl:apply-templates select="@*"/>
            <xsl:for-each select="node()">
                <xsl:choose>
                    <xsl:when test="self::text()">
                        <xsl:call-template name="insertElements" />
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:apply-templates select="." />
                    </xsl:otherwise>
                </xsl:choose>
            </xsl:for-each>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

这个是有效的,尽管它似乎也会在uicontrol标签中添加一个{xmlns=""}。 - Ace
这个失败了:<para> 这只是一个括号:[ </para> - user357812

2

这个转换:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:my="my:my" exclude-result-prefixes="my">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <my:uicontrols>
  <control>[Save]</control>
  <control>[Cancel]</control>
 </my:uicontrols>

 <xsl:key name="kHasControls" match="text()"
  use="boolean(document('')/*/my:uicontrols/*[contains(current(), .)])"/>

 <xsl:variable name="vControls" select="document('')/*/my:uicontrols/*"/>

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

 <xsl:template match="text()[key('kHasControls', 'true')]">
  <xsl:choose>
   <xsl:when test="not($vControls[contains(current(),.)])">
     <xsl:copy-of select="."/>
   </xsl:when>
   <xsl:otherwise>
    <xsl:call-template name="createControl"/>
   </xsl:otherwise>
  </xsl:choose>
 </xsl:template>

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

  <xsl:choose>
   <xsl:when test="not(contains($pText, '['))">
    <xsl:copy-of select="$pText"/>
   </xsl:when>
   <xsl:otherwise>
     <xsl:copy-of select="substring-before($pText, '[')"/>

     <xsl:variable name="vStartText" select=
     "concat('[', substring-after($pText, '['))"/>
     <xsl:variable name="vCtrl" select="$vControls[starts-with($vStartText,.)]"/>
     <xsl:choose>
      <xsl:when test="not($vCtrl)">
       <xsl:text>[</xsl:text>
       <xsl:call-template name="createControl">
         <xsl:with-param name="pText" select="substring($vStartText,1)"/>
       </xsl:call-template>
      </xsl:when>
      <xsl:otherwise>
       <uicontrol>
        <xsl:value-of select="translate($vCtrl,'[]','')"/>
       </uicontrol>

       <xsl:call-template name="createControl">
         <xsl:with-param name="pText" select="substring-after($vStartText, $vCtrl)"/>
       </xsl:call-template>
      </xsl:otherwise>
     </xsl:choose>
   </xsl:otherwise>
  </xsl:choose>
 </xsl:template>
</xsl:stylesheet>

当应用于提供的XML文档时

<section>
    <title>Buttons</title>
    <orderedlist>
        <listitem>
            <para>Clicking on [Save] will attempt to save changes, then it navigates to <xref linkend="saved" xrefstyle="select: title"/>.</para>
        </listitem>
        <listitem>
            <para>Clicking on [Cancel] navigates to <xref linkend="noSave" xrefstyle="select: title"/>.</para>
        </listitem>
    </orderedlist>
</section>

产生所需的、正确的结果:

<section>
    <title>Buttons</title>
    <orderedlist>
        <listitem>
            <para>Clicking on <uicontrol>Save</uicontrol><xref linkend="saved" xrefstyle="select: title"/>.</para>
        </listitem>
        <listitem>
            <para>Clicking on <uicontrol>Cancel</uicontrol><xref linkend="noSave" xrefstyle="select: title"/>.</para>
        </listitem>
    </orderedlist>
</section>

请注意以下内容:

  1. 此解决方案仅替换受控制的控件名称列表。这样可以避免意外错误,同时我们可以自由地使用类型为"[Anything]"的字符串而不会出现任何问题(例如,我们想要显示一个著名的XPath表达式--这种表达式根据定义具有谓词 :))。

  2. 使用键可确保更高效率,而不是扫描每个文本节点以查找"["


@Dimitre: +1 因为我喜欢使用固定的 [control] 方法来保留其他括号中的文本(正如您所写的 XPath 谓词!)。我还喜欢在键中使用 fn:current() (我不知道为什么之前一直认为它是被禁止的)。 - user357812
@Alejandro:当然,如果有一个明确定义的要求,只需要处理控件节点,那么密钥只能匹配这些文本节点。 - Dimitre Novatchev
@Dimitre:我这么说只是因为如果整个文档中至少有一个文本节点具有固定的[control],那么你最终将“扫描每个节点”。除此之外,我还喜欢节点集测试用于反向XPath表达式,例如$vControls[starts-with($vStartText,.)] - user357812
@Dimitre:这是一个非常精确的解决方案。虽然在这种情况下,我并不总是确定'['']'之间会有什么单词。 - Ace
@Ace:你应该更新你的问题,这样问题就更明确了,答案才会与你的问题相匹配,其他人也能从中受益。 - user357812
显示剩余4条评论

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