3

Bad wording on the question, sorry about that. Will try to explain what I'm trying to do. Basically I have the output from a search as Xml and in that Xml there is a node like this one:

<FIELD NAME="body">
  Somebody named 
  <key>Doris</key> 
  and 
  <key>Arnie</key> 
</FIELD>

In short, what I need is to replace "<key>" with "<strong>"; ie. highlight the search hits (the key node values are what the user searched for). In the Xslt I do not know what the user searched from, other than querying the Xml -> FIELD[@name='body']/key.

Right now I have some crazy code that will extract whatever is in front of the search term ("Doris"), but that ony works for 1 search term. We need it to do this for multiple terms. The code we use looks like this:

  <xsl:template name="highlighter">
    <xsl:param name="text"/>
    <xsl:param name="what"/>

    <xsl:choose>
      <xsl:when test="contains($text, $what) and string-length($what) &gt; 0">
        <xsl:variable name="before" select="substring-before($text, $what)"/>
        <xsl:variable name="after" select="substring-after($text, $what)"/>
        <xsl:variable name="real-before" select="substring($text, 1, string-length($before))"/>
        <xsl:variable name="real-what" select="substring($text, string-length($before) + 1, string-length($what))"/>
        <xsl:variable name="real-after" select="substring($text, string-length($before) + string-length($what) + 1)"/>
        <xsl:value-of select="$real-before"/>

        <strong>
          <xsl:value-of select="$real-what"/>
        </strong>

        <xsl:call-template name="highlighter">
          <xsl:with-param name="text" select="$real-after"/>
          <xsl:with-param name="what" select="$what"/>
        </xsl:call-template>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="$text"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

What I've been trying to do is to call this code multiple times with the different search terms, but I'm struggeling on how to use the output from the call to the template as input to the next call. In code it would be something like this:

string body = doc.SelectSingleNode("FIELD[@NAME='body']");
NodeCollection nodes = doc.SelectNodes("FIELD[@NAME='body']/key");
foreach (var node in nodes) {
    body = hightlighter(body, node.InnerText);   
}

So far I have been unable to do something like this in XSLT, but I'm still a noob so... ;)

Edit: Just to clarify; the output I'm looking for is this:

Somebody named <strong>Doris</strong> and <strong>Arnie</strong>

3 Answers 3

11

The best thing to do in such situations is to recursively copy nodes from input to output and override the nodes that you want to treat differently. The key idea is that the text strings are nodes which can be copied too. Here's an example:

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

<xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>
Sign up to request clarification or add additional context in comments.

5 Comments

This looks like Chris Dali's answer... If I use "<xsl:apply-templates select="FIELD[@NAME='body']/key" />" to apply the template all I get is the value of the "KEY" nodes in bold, that's not what I want... I need everything from the "FIELD" node.
Try selecting just FIELD[@NAME='body'] or even FIELD or /
You're a legend! :) But there is a typo in your Xslt; apply-templates does not allow "match"; I believe it should be "select". And it worked when I used "FIELD[NAME='body']". :) Thnx!!
Sorry. I've fixed the typo now.
You're welcome. See also my further explanation in the comments to Chris' version.
2

This should do what you need. It uses the apply template instead of calling templates and is more of a functional way to tackle this problem.

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <!-- Template to match your 'key' and replace with strong -->
    <xsl:template match="FIELD[@name='body']/key">
        <strong><xsl:apply-templates select="@*|node()"/></strong>
    </xsl:template>

    <!-- Template to match all nodes, copy them and then apply templates to children. -->
    <xsl:template match="@*|node()">
      <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
      </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

6 Comments

Hmm... How do I "invoke" the first template? I should perhaps mention that I already have quite a large XSLT and I need this replace inside a template that matches a parent node to "FIELD".
The second template matches all nodes in the document and simply produces a copy of them. The first template matches you specific FIELD/key you are looking for and replaces it with <strong>. This pattern is commonly used to have the output XML document be a copy of the original.
I'm sorry, but I didn't understand that last comment at all... I mean I can understand the words, but it makes no sense to me... (I'm a noob, please bear with me)
When you apply templates at a node or attribute, the XSLT processor will look for the most specific thing that matches. In all cases except when processing the key the most specific match will be the second template, which just codes the current node to the output and then recursively applies templates to all of its child nodes or attributes. If there weren't any keys inside fields with name="body" then this would just copy the whole input document to the output. However, when the processor encounters the key it creates a new "strong" element in the output and then moves onto the key's...
...children, adding those to the output too. The net result is that all the appropriate "key" elements in the input are converted to "strong" elements in the output and everything else is left unchanged (including text nodes).
|
1

Why can't you just replace the "KEY" element with "STRONG" elements? Better not to think too imperatively about this.

<xsl:template match="FIELD[@NAME='body']">
  <xsl:apply-templates/>
<xsl:template>

<xsl:template match="key">
  <strong>
  <xsl:apply-templates/>
  <strong>
</xsl:template>

<xsl:template match="text()">
  <xsl:copy-of select="."/>
</xsl:template>

Or did I misunderstand you?

3 Comments

Hmm... How do I "invoke" the first template? I've tried with: "<xsl:apply-templates select="FIELD[@NAME='body']" />" but that doesn't appear to work...
Just to clarify; the output I'm looking for is this: Somebody named <strong>Doris</strong> and <strong>Arnie</strong>
The template should match anyway, unless you changed the default template behaviour: the stylesheet should recurse the XML file until it finds the node. Does your XML file have namespaces? Did you bind the namespaces properly in your stylesheet?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.