[Solved] Adding Word content from a blob to a Word Report Layout

PConijnPConijn Member Posts: 31
edited 2018-03-28 in NAV Three Tier
Hi all,

I have a question not entirely dissimilar to this one.

Background Information
We have the option to store equipment configuration option specs in a Word document and attach it as a blob to that configuration option record. These specs contained text, tables and/or pictures, all in one.

In the "old days" (pre-Word Layout) I tinkered a way to load these specs as Word building blocks (autotext); the user could then add these in the Word report after generation.

Intended Change in Functionality
In the new structure with Word report layouts etc., we would like to use the Word layout to add these blobs to the Word layout, so that when an option is added to the configuration, the specs are automagically loaded and inserted at the placeholder.

For this, I would need to do two things:

1) Extract the content of the stored specs Word document or store only the content of the specs Word document - this is doable;
2) Somehow insert the content at the location of the placeholder.

Question
The question centers around aforementioned action no. 2: how do I insert that content (containing various elements) in that location at runtime?

In designing the Word layout, you need to insert the blob field as a text, RichText, picture, etc.
I can get the blob content in there as text, and even the Word content, but it's still xml and, naturally, text won't abide pictures and tables, so I will need to insert it at the xml level.

The merge process seems to take place in a closed NAV library (Microsoft.Dynamics.Nav.DocumentReport.WordReportManager), which means it's inaccessible code.

Any tips, hints or epiphanies?
Kind Regards,

Peter Conijn

-The Learning Network-

Best Answer

  • PConijnPConijn Member Posts: 31
    edited 2018-03-28 Answer ✓
    I solved it.

    Using the Microsoft.Office.Interop.Word libraries, I was able to loop through the content controls of the Word Report Layout at time of printing and look for a specific XPath in the CustomLayout xml (stored in the Custom Report Layout table).

    Using that XPath, I was then able to replace the content control with the content of the WordDocument.Content range of the previously stored Word blob.

    At this point it does require the report to use the actual table with the stored blob, given that the XPath search string is hard-coded, but that does not present practical issues at this point.

    Getting the XPath
    In order to get the correct XPath, please refer to this C# code snippet. It is usable as dll, but also not hard to code into NAV itself.

    We can now add pre-stored Word blobs to our Word report layout in runtime.

    We will need to test more, but this is the business part of the code.

    LOCAL FillBuildingBlockInWord(pTxtXPath : Text;pTxtBuildingBlockPath : Text)
    //Loop through the content controls in the printed Word Report layout
    FOREACH netWordContentControl IN netWordContentControls DO BEGIN
    
      //If the XPaths match, find the Word Block and replace the content control with it
      IF netWordContentControl.XMLMapping.XPath = pTxtXPath THEN BEGIN
    
        lNetContentControlRange := netWordContentControl.Range; //The range of the report Word Layout content control
    
        //Open the building block Word document we saved earlier
        lNetBBWordApp := lNetBBWordAppClass.ApplicationClass;
        lNetBBWordApp.Visible := FALSE;
        lNetBBWordDoc := netWordHelper.CallOpen(lNetBBWordApp, pTxtBuildingBlockPath, FALSE, FALSE);
        lNetBBContentRange := lNetBBWordDoc.Content; //Get the contents of the building block document
    
        //Try to add the building block content to the word document; if successful, delete the content contol (while leaving the data)
        IF TryAddContent(lNetContentControlRange, lNetBBContentRange) THEN
          netWordContentControl.Delete(FALSE);
    
        //Close the building block document
        netWordHelper.CallClose(lNetBBWordDoc, FALSE);
        CLEAR(lNetBBWordApp);
      END;
    END; //ForEach
    

    TryAddContent function:
    LOCAL [TryFunction] TryAddContent(VAR pRefNetWordRange : DotNet "Microsoft.Office.Interop.Word.Range" RUNONCLIENT;VAR pRefNetBuildingBlockRange : DotNet "Microsoft.Office.Interop.Word.Range" RUNONCLIENT)
    pRefNetWordRange.InsertXML(pRefNetBuildingBlockRange.XML(FALSE), lNetObject);
    
    Kind Regards,

    Peter Conijn

    -The Learning Network-

Answers

  • PConijnPConijn Member Posts: 31
    edited 2018-03-28 Answer ✓
    I solved it.

    Using the Microsoft.Office.Interop.Word libraries, I was able to loop through the content controls of the Word Report Layout at time of printing and look for a specific XPath in the CustomLayout xml (stored in the Custom Report Layout table).

    Using that XPath, I was then able to replace the content control with the content of the WordDocument.Content range of the previously stored Word blob.

    At this point it does require the report to use the actual table with the stored blob, given that the XPath search string is hard-coded, but that does not present practical issues at this point.

    Getting the XPath
    In order to get the correct XPath, please refer to this C# code snippet. It is usable as dll, but also not hard to code into NAV itself.

    We can now add pre-stored Word blobs to our Word report layout in runtime.

    We will need to test more, but this is the business part of the code.

    LOCAL FillBuildingBlockInWord(pTxtXPath : Text;pTxtBuildingBlockPath : Text)
    //Loop through the content controls in the printed Word Report layout
    FOREACH netWordContentControl IN netWordContentControls DO BEGIN
    
      //If the XPaths match, find the Word Block and replace the content control with it
      IF netWordContentControl.XMLMapping.XPath = pTxtXPath THEN BEGIN
    
        lNetContentControlRange := netWordContentControl.Range; //The range of the report Word Layout content control
    
        //Open the building block Word document we saved earlier
        lNetBBWordApp := lNetBBWordAppClass.ApplicationClass;
        lNetBBWordApp.Visible := FALSE;
        lNetBBWordDoc := netWordHelper.CallOpen(lNetBBWordApp, pTxtBuildingBlockPath, FALSE, FALSE);
        lNetBBContentRange := lNetBBWordDoc.Content; //Get the contents of the building block document
    
        //Try to add the building block content to the word document; if successful, delete the content contol (while leaving the data)
        IF TryAddContent(lNetContentControlRange, lNetBBContentRange) THEN
          netWordContentControl.Delete(FALSE);
    
        //Close the building block document
        netWordHelper.CallClose(lNetBBWordDoc, FALSE);
        CLEAR(lNetBBWordApp);
      END;
    END; //ForEach
    

    TryAddContent function:
    LOCAL [TryFunction] TryAddContent(VAR pRefNetWordRange : DotNet "Microsoft.Office.Interop.Word.Range" RUNONCLIENT;VAR pRefNetBuildingBlockRange : DotNet "Microsoft.Office.Interop.Word.Range" RUNONCLIENT)
    pRefNetWordRange.InsertXML(pRefNetBuildingBlockRange.XML(FALSE), lNetObject);
    
    Kind Regards,

    Peter Conijn

    -The Learning Network-

  • slavikdem@live.comslavikdem@live.com Member Posts: 1
    Hi Peter,

    I want to know from where to run your code and variables of what types in your function?

    I need is to add additional pages to a word document created from a report using the saveaspdf function with the content from another word documents stored in NAV as attachment to the Sales Quote. As I understand, it is possible with your method?

    Regard,
    Vyacheslav
  • PConijnPConijn Member Posts: 31
    Hi Vyacheslav,

    Thank you for reaching out. Yes, it's certainly possible using this, but you don't need to loop through content controls like I do, nor do you need the whole bit about XPath.

    I would remind you, however, that this solution was based on C/AL and uses .NET interop, which, going forward, will be tougher in AL.

    The functionality can be called in codeunit 9651, in the MergeWordLayout function, just after the line:
    OutStrWordDoc := NAVWordXMLMerger.MergeWordDocument(InStrWordDoc,InStrXmlData,OutStrWordDoc) ;
    


    Almost all of the .NET variables all come from the Microsoft.Office.Interop.Word assembly.
    lNetBBWordApp = Microsoft.Office.Interop.Word.Application
    lNetBBWordDoc = Microsoft.Office.Interop.Word.Document
    lNetBBContentRange = Microsoft.Office.Interop.Word.Range
    lNetContentControlRange = Microsoft.Office.Interop.Word.Range
    
    netWordContentControl = Microsoft.Office.Interop.Word.ContentControl
    

    WordHelper is a Dynamics NAV assembly:
    netWordHelper = Microsoft.Dynamics.Nav.Integration.Office.Word.WordHelper
    

    It always helps me to search for similar code in C# and translate that back to C/AL. StackOverflow specifically often gives you a lot of information.
    Kind Regards,

    Peter Conijn

    -The Learning Network-

Sign In or Register to comment.