Business central blog
Subscribe to blog and get news about new posts.

Dynamic viewing HTML content on Word Template reports. Convert docx to html and vice versa in Business Central. AL + JS.

We all know that Visual Studio and the RDLC report format partially support the dynamic rendering of HTML content from the control. But only partially, many tags are simply ignored. And if we have a word template, what can we do in this case? Today I will share the result of my analysis and provide a working example code for word template reports. We will also learn how to convert docx to html and vice versa.

Word HTML control?

I started to analyze whether it is possible to simply add HTML control to the Word Template? Unfortunately, this is not supported out of the box. I even found several ways to do this using Word API JS or through .NET AltChunk class. But it was important for me not to depend on any third-party applications or Azure Functions. Can I just install the Extension and use the functionality? Looking ahead, I will answer - yes!
Having tried many methods and ideas, I eventually came up with this algorithm:
  1. Prepare Word Template report and add simple text control for HTML content.
  2. Render and save report as HTML by standard function Report.SaveAs().
  3. Replace characters references by characters in result HTML.
  4. Convert back HTML to DOCX with correct content in tags.
  5. Import result DOCX to Custom Report Layout.
  6. Run report from Custom Report Layout with all related data.
  7. Delete Custom Report Layout. (Since content can change dynamically, we need to keep track of the relevance of the data.)

Implementation of the idea

Prepared a simple Word template that extracts a text value from a record that stores HTML.
If we import several examples of HTML content and run the report, then as a result we will see something like this:
Of course, we are not satisfied with this result. Therefore, we will convert the report to HTML using the standard function Report.SaveAs. After that, it is imperative to replace all HTML symbol references with specific symbols.
procedure ConvertReportToValidHTML(ReportId: Integer): Text
var
    TempBlob: Codeunit "Temp Blob";
    OutStreamVar: OutStream;
    InStreamVar: InStream;
    ReportAsHTML: Text;
begin
    TempBlob.CreateOutStream(OutStreamVar);
    Report.SaveAs(ReportId, '', ReportFormat::Html, OutStreamVar);
    TempBlob.CreateInStream(InStreamVar);
    InStreamVar.ReadText(ReportAsHTML);
    exit(ReplaceInvalidHTMLCharacters(ReportAsHTML));
end;

procedure ReplaceInvalidHTMLCharacters(pText: Text): Text
var
    lText: Text;
begin
    lText := pText;
    lText := lText.Replace('&', '&');
    lText := lText.Replace('&lt;', '<');
    lText := lText.Replace('&gt;', '>');
    lText := lText.Replace('&quot;', '"');
    lText := lText.Replace('&apos;', '''');
    exit(lText);
end;
Already at this stage, we can get the result as an HTML file and download it! But we will go ahead and try to convert HTML to DOCX to import it as a Custom Report Layout. In order not to depend on third-party resources, I got the idea that there probably already exist JavaScript libraries that can be integrated into our Extension. Indeed, I managed to find a very handy html-to-docx-js library. Since this library returns BlobBuffer, I also needed an additional File-Saver library with which you can convert the buffer to Base64 as text or even download the result. Here is an example of using these two libraries to convert HTML to DOCX and download the result on JavaScript:
function DownloadAsDocx(HTMLData,FileName){

  var converted = htmlDocx.asBlob(HTMLData);

  var reader = new FileReader();
  reader.readAsDataURL(converted); 
  reader.onloadend = function() {
      var base64data = reader.result;
      saveAs(base64data, FileName);                
  }

}
Finally, we got a valid Word document! Or not? If you try to import such a Word document into Business Central you will fail with the error:
Unfortunately, debugging this error does not lead to anything, since the error is thrown out on a standard .NET variable that validates the Word Template on import into the Custom Report Layout. Since there is no way to monitor this black box in Business Central, I had to stop before coming up with a new idea.
I remembered that DOCX, like any OpenXML format, contains some XML structure inside itself, which means that the error is quite likely due to an incorrect structure. Besides, any Word document can be opened using an archiver, for example, WinRar, did you know?
Therefore, I unloaded the valid .docx and compared it with the invalid one that I had previously generated.
The differences can be seen with the naked eye, so I analyzed what really important was missing from my Word document. It turned out that the docProps folder and everything connected with it is needed by the Word validator in Business Central. To fix this problem, you need to go to the html-to-docx-js library and find the place that is responsible for generating the internal XML structure. Next, we simply supplement the library with the data we need, here is an example of an already changed structure (the content is Base64 which is converted to XML):
After that, the import will be successful and we just have to write a function that will convert Base64 to binary data and import the result into the Custom Report Layout with the subsequent launch of the report.
local procedure RunReportWithHTMLContent(Base64DOCX: Text)
var
    CustomReportLayout: Record "Custom Report Layout";
    Base64Convert: Codeunit "Base64 Convert";
    OutStreamVar2: OutStream;
    DOCXAsTxt: Text;
begin
    CustomReportLayout.Get(CustomReportLayout.InitBuiltInLayout(Report::"WHTML HTML Content View", CustomReportLayout.Type::Word.AsInteger()));
    CustomReportLayout.Layout.CreateOutStream(OutStreamVar2);
    DOCXAsTxt := Base64DOCX.Remove(StrPos(Base64DOCX, Base64DocxAliasLbl), StrLen(Base64DocxAliasLbl));
    Base64Convert.FromBase64(DOCXAsTxt, OutStreamVar2);
    CustomReportLayout.Modify();
    Commit();
    CustomReportLayout.RunCustomReport();
    CustomReportLayout.Delete();
end;

Demo

I have created a page that can be used to import HTML content. And also a report with Word Layout which in turn contains a simple text control. In the "HTML Examples" folder you can find examples of HTML to import. Actions on this page:
  • Add Content - import .html content to table
  • View Content - run valid report with .html content from the table
  • Download - download report as .html or .docx
View content
Download

Summary

Several times in the search for solutions, I encountered difficulties that seemed insurmountable. But every time I returned to this question after a rest. So, I got a satisfying result, but this is still not a complete version. There is nothing better in programming than solving unusual problems. I wish everyone not to give up and go to their result!

Source code

MONDAY, April 26, 2021