tags:

views:

294

answers:

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>
+1  A: 

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?

xcut
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...
noocyte
Just to clarify; the output I'm looking for is this: Somebody named <strong>Doris</strong> and <strong>Arnie</strong>
noocyte
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?
xcut
No namespaces in the Xml.
noocyte
+3  A: 

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>
Rich
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.
noocyte
Try selecting just FIELD[@NAME='body'] or even FIELD or /
Rich
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!!
noocyte
Sorry. I've fixed the typo now.
Rich
I'm the one that should be thanking you ;)
noocyte
You're welcome. See also my further explanation in the comments to Chris' version.
Rich
+2  A: 

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>
Chris Dail
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".
noocyte
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.
Chris Dail
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)
noocyte
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...
Rich
...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).
Rich
I think I sort of understand this... Thanx for the extended comment, appreciate it.
noocyte