We all know that Visual Studio and the RDLC report format partially support 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.
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:
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('<', '<');
lText := lText.Replace('>', '>');
lText := lText.Replace('"', '"');
lText := lText.Replace(''', '''');
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;
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:
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 is available on github:
https://github.com/Drakonian/business-central-view-html-on-docx-report