Search code examples
velocityconfluence

How can I get #evaluate to put its results into the page scope?


I cannot get #evaluate to set a variable using any accepted solution I've seen for turning a JSON string into a map using Velocity in Confluence.

I have a JSON string coming from another system that I use to generate dynamic data. During development, I was able to work with the map directly:

#set($textMapRaw = [{"ace":"clubs"}])

$textMapRaw.get(0).ace

The last line renders as "clubs".

I had expected to use #evaluate to turn the live data into the array of objects. However, it seems that #evaluate is not putting the declared variable back into scope when I do anything with it. Here's the same map coming in as a string:

#set($textMapHtml = '[{"ace":"clubs"}]')
#set($evalHtml = '#set($myMapHtml = '+ $textMapHtml +')')
evalHtml = $evalHtml<br/>
#evaluate($evalHtml)

$myMapHtml.get(0).ace

The last line renders as "$myMapHtml.get(0).ace".

I know that $myMapHtml is being evaluated because I was getting an evaluate stack-trace when I didn't append Html to the end of it to avoid Confluence's auto-escaping shenanigans.

I've tried it with escaping the hash in set using ${hash}set. I've tried single quotes and double quotes. I've tried it with just setting a string via evaluate:

#evaluate('#set($dippity = "doo")')
$dippity

#set($hash = '#')
#evaluate("${hash}set($blue = 'moon')")
$blue

No matter what I do with any ratified #evaluate code, it is never evaluated into scope in Confluence. I always get a result identical to an undefined variable.

Note: In our environment, I cannot do a workaround in Java and push it into the template. I need to find a pure, in-template solution.


Solution

  • Since it appears you cannot get the variable back into the current scope, I figured out a workaround. Just use evaluate to pass the array/map to a macro.

    You still run into scope issues if you try to manipulate variables outside the evaluated code or the macro it calls, but if you're careful you can code around it.

    In my case, I moved another bit of code inside the macro to take advantage of the local scope to determine how many items were processed.

    #macro(processMap $theMap)
      #set($count = 0)
      #foreach($item in $theMap)
        #set($count = $velocityCount)
        <p>This thing is $item.tweedle</p>
      #end
      <p><b>I did that $count times</b></p>
    #end
    
    #set($workaroundHtml = '[{"tweedle":"dee"},{"tweedle":"dum"}]')
    
    #set($eval = '#processMap(' + $workaroundHtml + ')')
    
    #evaluate($eval)