An example of the manipulation of HTML code generated by a macro

I have been working with XWiki for several years now, but I have always missed the routine
$doc.getRenderedContent(html,syntax)

Well, now I have used this as an example for an “afterburner” of the code macro.
I called it “codeX” it has the same parameter like code but some additional

  • range restricts the output via line ranges or regular expressions
  • rangelayout to change the CSS layout (typically the colour) afterwards for certain areas
  • style to pass through CSS specifications directly

An example:

codeX example

For those interested, the code:

{{groovy}}
knownParameter=["cssclass","image","title","width","language","layout","source"]
givenParameter=xwiki.context.macro.params.getParameterNames()
paramLayout=xwiki.context.macro.params.get("layout")

paramStyle=xwiki.context.macro.params.get("style")             // just additional css style
paramRange=xwiki.context.macro.params.get("range")             // line range = "which lines should be printed"
                                                               // alternativ:
                                                               // a) like GROOVY slice syntax (e.g: range="1..3,5,7" gives the first 3 lines, followed by 5th, 7th) but "1" is the first line number
                                                               //    range="1..3;5;7" generates an additional empty line between the line blocks
                                                               // b) or a regular expression (surrounded by slashes)
                                                               //    e.g.  range="1..10;/^#/" prints the first 10 lines and additional all lines starting with "#"
paramRangeStyle=xwiki.context.macro.params.get("rangestyle")   // modify linew style, ("##" separates multiple directives); examples:
                                                               //  rangestyle="1..3,7..9 || color:red ## -3..-1 || color:blue"
                                                               //         -> lines 1 to 3 and 7 to 9 will be red, the last 3 lines blue 
                                                               //  rangestyle="1..3;7..9 || color:red ## -3..-1 || color:blue"
                                                               //  rangestyle="|| color:red;font-weight:bold ||^.*x.*$"
                                                               //         -> all lines containing a "x" will be bold and red

// build original {{code ..}} call and get the HTML result
  
theOriginalCode="{{code "
givenParameter.each { theOriginalCode += " "+it+"="+'"'+xwiki.context.macro.params.get(it).replaceAll(/"/,'~"')+'"' }
theOriginalCode += "}}\n"+xwiki.context.macro.content+"{{/code}}"
theHTML=doc.getRenderedContent(theOriginalCode,"xwiki/2.0")

// split the HTML result in arrays
// the HTML structure is different, when linenumbers are used

Boolean withLineNumbers= (paramLayout != null)  && paramLayout.toLowerCase().equals("linenumbers")
if (withLineNumbers) { 
  m=theHTML =~ /(?s)^(.*?<div class="linenos">)(.*?)(<br\/><\/div><div>)(.*?)(<\/div><\/div><\/div><\/div>)/
  m1=m[0][1]

  if(paramStyle!=null) { m1=m1.replaceAll(/class="box/,'style="'+paramStyle+'" class="box ')}    // insert new global style value
  m2="a<br/>"+m[0][2]   // dummy element with technical index 0
  m3=m[0][3]
  m4="a<br/>"+m[0][4]   // dummy element 
  m5=m[0][5]
  theNumbers= m2.split(/<br\/>/)
  theInfo =m4.split(/<br\/>/)
} else {
  m=theHTML =~ /(?s)^(.*?<div class="code">)(.*?)(<\/div><\/div>)/
  m1=m[0][1]
  m2="a<br/>"+m[0][2]  // dummy element 
  m3=m[0][3]
  if(paramStyle!=null) { m1=m1.replaceAll(/class="box/,'style="'+paramStyle+'" class="box ')} // insert new global style value
  theNumbers=[]
  theInfo =m2.split(/<br\/>/)
}

// handle paramRangStyle

if (paramRangeStyle != null) {
     indexes=[]
     (0..theInfo.size()-1).each { indexes << it }
     paramRangeStyle.split("##").each { rangestyle ->
           rr=rangestyle.split(/\|\|/)                    // split e.g. "1..5||color:red||^.*x.*$"
           if (rr[0]=="") {rr[0]="1..-1"}
           needed=Eval.x(indexes,"x[${rr[0]}]") 
           if (rr.size()>2) {
               needed.each { m = theInfo[it] =~ rr[2]
                            if (m) {
                                 theInfo[it] = '<span style="'+rr[1]+'">'+theInfo[it]+"</span>" } } 
           } else {
               needed.each { theInfo[it] = '<span style="'+rr[1]+'">'+theInfo[it]+"</span>" }
           }
       }
}

// handle paramRange

if (paramRange==null || paramRange=="") { paramRange="1..-1" }
  numResult=""
  txtResult=""
  if (withLineNumbers) { // with linenumbers
     paramRange.split(/;/).each { range -> 
              if (range.matches(/\d+/)) {range+=".."+range}   // a single number is NOT a range !
              if( range.startsWith("/")) {
                regExp=range.replaceAll("/","")
                Boolean firstMatch=true
                theInfo.eachWithIndex{ it,index -> 
                   clearedIt = it.replaceAll(/<span.*?>/,"").replaceAll("</span>","")
                   if (index>0) {
                     m = clearedIt =~ regExp
                     if (m) {
                         if (firstMatch && txtResult !="") { numResult +="<br/><br/>"; txtResult+="<br/><br/>"}
                         if (!firstMatch) {numResult +="<br/>"; txtResult+="<br/>"}
                         numResult += theNumbers[index]
                         txtResult += it
                         firstMatch=false
                     }
                   }
                }
              } else {
                  thx=Eval.x(theNumbers,"x[${range}]")
                  thi=Eval.x(theInfo,"x[${range}]")
                  if(txtResult != "") { 
                     numResult += "<br/><br/>" 
                     txtResult += "<br/><br/>" 
                  }
                  numResult += thx.join("<br/>")
                  txtResult += thi.join("<br/>")
             }
     }
     theHTML =  m1+numResult+m3+txtResult+m5
  } else { // without linenumbers
     paramRange.split(/;/).each { range -> 
              thi=Eval.x(theInfo,"x[${range}]")
              if(txtResult != "") { 
                 txtResult += "<br/><br/>" 
              }
              txtResult += thi.join("<br/>")
     }
     theHTML =  m1+numResult+txtResult+m3
  }
print "{{html clean='false'}}"+theHTML+"{{/html}}"
{{/groovy}}

(developed under 14.10.7)

All comments are welcome.

Norbert

Thanks @NorSch !

Could be something to put on https://snippets.xwiki.org/xwiki/bin/view/Main/WebHome

Another idea would be to develop a new XWiki macro that uses Prism. There are some interesting plugins that could do what you’re looking for, see Prism

We also have the highlight.js macro but it has less options from what I see.