Sibling Axis workaround: getting to nodes before and behind

Hi,
I thought I’d provide a sample solution to a problem I have had since 2002 but which at a new attempt I was able to solve using Tamino 4.4.1.

The problem: I needed to access an interval around a node, that is n sibling nodes before and m sibling nodes behind a node. And the node itself would be nice as well. Take something like

<paragraph> 
   <word>Sample</word> 
   <word>for</word> 
   <word>a</word> 
   <word>context</word> 
   <word>based</word> 
    <word>search</word> 
    <word>like</word> 
    <word>a</word> 
    <word>concordance</word> 
</paragraph> 

I want to have something like two nodes before “search” and one behind, the result should be in the same structure but without all the other words. In fact this is meant to be used as a concordance. Previous attempts were problematic, but here is the solution that works. Not pretty, but it does what it is supposed to do. I thought this kind of query could be of some interest to others, so I post it here without accepting any liabilities :slight_smile: If someone has a prettier or more elegant version, feel free to post it.

There is some redundancy left in for debugging purposes. The construct for the upper limit restricts the maximum number of iterations, just to be on the safe side, if a bug is included then this really safes a lot of waiting time.
BTW you might need to remove the comments, at least the Interactive Interface does not like them, though my debugger does not complain.

 
(:  These are the context functions, requiring the number of words before and behind, the context, a counter and an upper limit :) 
 
declare function local:leftcontext($searchword as element(), $counter, $upperlimit, $wordsbefore) as element()* 
{ 
for $context in $searchword/.. 
return   
      if ($context/word[$counter] = $searchword) 
      then  
      <leftcontext> 
{      $context/word[position() < $counter and position()> $counter - $wordsbefore -1 ] } 
      </leftcontext> 
      else  
          if ($counter<=$upperlimit)  
          then       local:leftcontext($searchword, $counter + 1, $upperlimit, $wordsbefore) 
          else <debug>{$counter},  {$upperlimit}, {$searchword},  
          <context>{$context}</context></debug> 
}; 
 
declare function local:rightcontext($searchword as element(), $counter, $upperlimit,  $wordsbehind) as element()* 
{ 
for $context in $searchword/.. 
return   
      if ($context/word[$counter] = $searchword) 
      then  
      <rightcontext> 
{      $context/word[position()  > $counter and position() < $counter + $wordsbehind +1] } 
      </rightcontext> 
      else  
          if ($counter<=$upperlimit)  
          then local:rightcontext($searchword, $counter + 1, $upperlimit,  $wordsbehind) 
          else <debug>{$counter},  {$upperlimit}, {$searchword},  
          <context>{$context}</context></debug> 
}; 
 
(: This function provides the information necessary for a counter and  
    concatenates the right and left contexts with the searchword in between  :) 
 
declare function local:countcontext($searchword as element(), $wordsbefore , $wordsbehind) as element()* 
   {  
   for $context in $searchword/.. 
   let $positions := count($context/word) ,  
         $counterinitial := 1 
    return  
	 <paragraph> 
	 { 
             local:leftcontext($searchword , $counterinitial , $positions, $wordsbefore )  
             } 
            <searchword>{$searchword/text()}</searchword>  
            { 
             local:rightcontext($searchword , $counterinitial , $positions, $wordsbehind ) 
             } 
             </paragraph> 
 }; 
 
(: The real function :) 
 
for $searchword in collection("SAMPLECOLLECTION")//paragraph 
let $wordsbefore := 2, 
    $wordsbehind := 1, 
    $searchwordstring := "search" 
where $searchword/text()= $searchwordstring 
return local:countcontext($searchword, $wordsbefore, $wordsbehind ) 

May it be useful for somebody else…