HTML to Wiki Markup: Since Jira 9.10 nekohtml library not found by atlassian renderer

Gabriele Talarico
I'm New Here
I'm New Here
Those new to the Atlassian Community have posted less than three times. Give them a warm welcome!
December 6, 2023

Hi All,

A method that worked up until Jira 9.9 no longer works. The error occurs from Jira 9.10 onwards.
The method is as follows:

public String fromHtmlToWiki(String txtHtml) {
         DefaultWysiwygConverter converter = new  DefaultWysiwygConverter();
         return converter.convertXHtmlToWikiMarkup(txtHtml);
}

This method throws a NoClassDefFoundError on the nekohtml library:


java.lang.NoClassDefFoundError: org/cyberneko/html/parsers/DOMFragmentParser
at com.atlassian.renderer.wysiwyg.converter.DefaultWysiwygConverter.convertXHtmlToWikiMarkup(DefaultWysiwygConverter.java:346)

going to see the jira folders atlassian-jira/WEB-INF/lib in versions prior to 9.10 there were both jars:
 -    atlassian-renderer-9.0.1.jar
 -    nekohtml-1.9.19.jar
while from jira 9.10 the nekohtml jar no longer exists in lib folder but exists in atlassian-bundeld-plugins folder.
I think this causes the above error.


How to solve it? Thank you!

2 answers

4 votes
Stefan Aschendorf
I'm New Here
I'm New Here
Those new to the Atlassian Community have posted less than three times. Give them a warm welcome!
January 8, 2024

Same issue here and a fix:

we put nekohtml.jar into /opt/atlassian/jira/lib/

after that, xerces is missing. get xerces from apache and put it in the lib folder too.

then restart jira

Muhammad April 3, 2024

Hi @Stefan Aschendorf thanks for sharing the trick, can you please share little more details on this

 

are both jar files and from where you downloaded those

Stefan Aschendorf
I'm New Here
I'm New Here
Those new to the Atlassian Community have posted less than three times. Give them a warm welcome!
April 3, 2024

Hi Muhammad,

yes, both are jar's and you find all here: https://nekohtml.sourceforge.net/

1 vote
Peter-Dave Sheehan
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
February 15, 2024

I built my own groovy class to help with that. It may not work in all use cases, but it might be a starting point for yours.

 

import groovy.util.logging.Log4j
import org.apache.log4j.Level
import org.jsoup.Jsoup
import org.jsoup.nodes.*
/**
* This is quickly put together class to convert HTML (typically from an Insight/Asset object rich text attribute)
* to a jira wiki string (to be used in comments).
* Not all HTML tags are supported.
* This was implemented because DefaultWysiwygConverter.convertXHtmlToWikiMarkup stopped working in Jira 9.12.0
* https://community.atlassian.com/t5/Jira-Software-questions/HTML-to-Wiki-Markup-Since-Jira-9-10-nekohtml-library-not-found/qaq-p/2553062
*/
@Log4j
class HtmlWikiConversionUtil {
StringBuilder wiki
Document doc
String convertHtmlToWiki(String html){
wiki = new StringBuilder()
doc = Jsoup.parseBodyFragment(html)
doc.body().childNodes().each{
processChildNode(it)
}
wiki.toString()
}

void processChildNode(Node node, Integer listDepth=0, String listChar=''){
if(node instanceof TextNode) {
wiki << node.text()
return
}
switch (node.nodeName()){
case 'p':
wiki << "\n\n"
break
case 'ul':
listDepth++
listChar = '*'
break
case 'ol':
listDepth++
listChar = '#'
break
case 'li':
wiki << "\n${listChar*listDepth} "
break
case 'strong':
wiki << "*"
break
case 'em':
wiki << '_'
break
case 'u':
wiki << '+'
break
case 'a':
wiki << '['
break
case 'br':
wiki << '\n'
default:
log.warn "${node.nodeName()} tag not currently supported. Only text will be kept out of this HTML: ${node.outerHtml()}"

}

node.childNodes().each{
processChildNode(it, listDepth, listChar)
}

// close the tag/markup
switch (node.nodeName()){
case 'p':
wiki << "\n"
break
case ['ol', 'ul']:
listDepth--
break
case 'strong':
wiki << "*"
break
case 'em':
wiki << '_'
break
case 'u':
wiki << '+'
break
case 'a':
def url = node.attr('href')
wiki << "|$url]"
break
}

}

}
Brent Nye February 21, 2024

Peter-Dave, thank you for contributing this. It looks like a great solution, but I'm having trouble figuring out how to leverage it in a ScriptRunner Behavior. Sorry to be needy, but any tips? Sincere thanks.

Peter-Dave Sheehan
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
February 21, 2024

@Brent Nye can you give me a bit more context on what you are trying to achieve in your behaviour?

But here are some generic tips.

  1. Make sure your scriptrunner/jira installation is configured to use script roots correctly
  2. Create a new file via the Script Editor in a location of your choice and paste my code. The file name must be exactly "HtmlWikiConversionUtil.groovy"
  3. Include, at the top of the file a package declaration that matches the path where you installed the file. So if your file is stored as  com/acme/util/HtmlWikiConversionUtil.groovy, the package line will be
    package com.acme.util
  4. Now you are ready to import and use the class in a behaviour script:
    import com.acme.util.HtmlWikiConversionUtil 

    def assetField = getFieldByName('asset field')
    def desc = getFieldById('description')

    def assetObject = Assets.getByKey(assetField.value[0]) //watch it here, there are some context where single values are returned instead of list, also check for empty, and figure how you want to handle multiple
    def assetHtmlText = assetObject.getString('rich text attribute')

    def wikiText = HtmlWikiConversionUtil.convertHtmlToWiki(assetHtmlText)

    desc.setFormValue(wikiText)
Like Brent Nye likes this
Brent Nye February 22, 2024

@Peter-Dave Sheehan , thank you for the detailed instructions. I'd obviously not implemented my own classes yet, so your instructions were extremely helpful.

Before upgrading to 9.12, I had a working solution (ScriptRunner behavior) which used 

com.atlassian.renderer.wysiwyg.converter.DefaultWysiwygConverter. Our help desk technicians would select a value from an Assets custom field. The Behavior would grab the Assets object and a specific field value from it, convert that to Jira wiki format, and dump it into the Jira field.
For testing purposes, I put your class script on the root and modified your solution to fit what I had previously. Here's what I tried:
import HtmlWikiConversionUtil

def assetField = getFieldByName('Service Desk Template')
String assetValue = assetField.getValue()

def assets = Assets.search('Name = \'' + assetValue + '\'')
def assetObject = assets[0]
def assetHtmlText = assetObject.getString('Remedy Information')

def
wikiText = HtmlWikiConversionUtil.convertHtmlToWiki(assetHtmlText)
def RI = getFieldByName("Remedy Information")
RI.setFormValue(wikiText)
That resulted in the import line being flagged with:
The [HtmlWikiConversionUtil] import is never referenced
And the line where I called your code was flagged with:
[Static type checking] - Non-static method HtmlWikiConversionUtil#convertHtmlToWiki cannot be called from static context
Google helped me realize I hadn't made an instance of that class. I did so as follows:
HtmlWikiConversionUtil htmlWikiConversionUtil = new HtmlWikiConversionUtil()
I'm thinking I shouldn't need to, as I initially used your instructions quite specifically. Either way, I and our help desk are deeply in your debt. Sincere thanks for your excellent solution, which converts bold/strong and bullet/UL tags into Jira wiki markup perfectly!
Peter-Dave Sheehan
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
February 22, 2024

No, my bad, the instructions were slighly incorrect. 

I had forgotten how the class was implemented (non-static), and wrote the example from memory.

This should work

def wikiText = new HtmlWikiConversionUtil().convertHtmlToWiki(assetHtmlText)
Like Brent Nye likes this
Peter-Dave Sheehan
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
February 22, 2024

It took me a while working with scriptrunner to understand the difference between static and non-static classes and methods (I don't have any formal software development education).

The short and long of it is that if a class has global variables that are accessed by the methods then you need to create an instance of that class. 

A static method makes no use of class-wide variables (unless those variables are constant/final).

Like Brent Nye likes this
Muhammad February 22, 2024

Hi @Peter-Dave Sheehan that's great but has some missing tags like table

Peter-Dave Sheehan
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
February 22, 2024

@Muhammad yeah, that's a starting point.

So far, in the way I have had to use this (copying Asset rich text into Jira wiki rich text field), I've never needed tables.

Feel free to expand on it.

Like # people like this

Suggest an answer

Log in or Sign up to answer