views:

60

answers:

3

This one is a bit tricky, and I've been stuck on it for a bit of time. What I wish to do is put tags in place of brackets '[' (e.g. for buttons, links, etc), and in place of ']'

<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>

And the '[' ']' is not necessarily always in section.listitem.para

Edit: I only need [] replacement when certain words are in the brackets.

+1  A: 

You can work with the contains, substring-before and substring-after functions to find the brackets and then insert the elements as you need them instead of the brackets.

Edit - that should work:

<?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>
Lucero
This works, although it seems to also add a {xmlns=""} to the uicontrol tag
Ace
This fails with `<para> This is just a bracket: [ </para>`
Alejandro
+3  A: 

For no nested uicontrol for nested brackets (that would need parsing for balanced brackets vs. no balanced brackets).

This stylesheet:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
 <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>

Output:

<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>

And with this input:

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

Output:

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

Note: Any text matching \[[^\[\]]*\] would be wraped into uicontrol element.

Alejandro
This works like the other solution, but also generates a certain warning several times:Engine name: Saxon6.5.5Severity: warningDescription: Ambiguous rule match for /section[1]/text()[1]Matches both "text()" on line 7
Ace
@Ace: That's just a warning because template confict resolution. Now it's gone with explicit `@priority`. Also, do note that answers are different. Mine preserves lonely brackets
Alejandro
Oh, right you are. And priority fixed things
Ace
@Alejandro: Good answer, +1. I would appreciate your opinion on my answer, too.
Dimitre Novatchev
@Ace: Looking into @Dimitre's answer I came to think that is better approach: fixed text in brackets to search. It makes the problem less general so you don't have to worry about nested or lonely brackets. Do note that for nested bracket you will need parsing!!
Alejandro
@Alejandro: Yes both answers will work for this specific question. But, the document I have doesn't have nested [ ] and I don't necessarily know what is always going to be between them either.
Ace
@Ace: Then this should help you. Just for clarification, suppose this text `This is bracket a bracket [ ? [Yes] [No]`: do you want this result `This is bracket a bracket [ ? <uicontrol>Yes</uicontrol> <uicontrol>No</uicontrol>` or this result `This is bracket a bracket <uicontrol> ? [Yes</uicontrol> <uicontrol>No</uicontrol>`?
Alejandro
@Alejandro: The former case, have each yes and no in their own uicontrol with no '[' ']'
Ace
@Ace: Check my edit for any text between brackets, with no nested `uicontrol`.
Alejandro
@Alejandro: Yes, as far as I can tell it works perfectly
Ace
+3  A: 

This transformation:

<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>

when applied on the provided XML document:

<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>

produces the wanted, correct result:

<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>

Do note the following:

  1. This solution only replaces a controlled list of control names. In this way we are protected from accidental errors and also we can freely use strings of type "[Anything]" without any problems (e.g. we want to display a famous XPath expression -- such expression by definition has predicates :) )

  2. The use of keys assures better efficiency than scanning every text node for "[".

Dimitre Novatchev
@Dimitre: +1 Because I like the fixed `[control]` approach in order to preserve other text in brackets (XPath predicates, as you wrote!). I also like the use of `fn:current()` in keys (I don't know why I always thought it was prohibited).
Alejandro
@Dimitre: As a contribution, I wouldn't use that "in whole document" test... If you define the key as the boolean value plus the node id, you could efficiently test for "this text node satisfy"
Alejandro
@Alejandro: Of course, if there is a well-specified requirement which only nodes to process for controls, the key can match only these text nodes.
Dimitre Novatchev
@Dimitre: I'm saying that only because you will end up "scanning every node" if at least one text node in the whole document has one of the fixed `[control]`. Besides that I also like the node set test for a reverse XPath expression like `$vControls[starts-with($vStartText,.)]`
Alejandro
@Alejandro: I know, you mean that every text node will need to be scanned. Yes, but there is a technique to generate all keys on the first reading and parsing of the XML document, so there is no additional price.
Dimitre Novatchev
@Dimitre: This is a very precise solution. Although in this case, I'm not always sure what word(s) are going to be between the '['']'
Ace
@Ace: It is definitely better to restrict and control the possible uicontrol names, because otherwise you could potentially get any rubbish as uicontrol and this will also malform the intended original text. As shown in my solution, this is also more efficient. :)
Dimitre Novatchev
@Dimitre: Right you are. It's best to control how its being used.
Ace
@Ace: You should update your questions now that is better defined, so answers will match your question and other people could get benefit.
Alejandro