FORUM PLAN UPDATE
Date revision: This forum will remain open to new posts and responses until December 1, 2018. (After that date, you will still be able to view and search the forum.) Also, we're taking a second look at the best place to host future conversation. For now, keep using this forum, and stay tuned for more news.



May 30, 2016, 11:33 AM
1 Posts

Adding a file as MIMEPART

  • Category: Server Side JavaScript
  • Platform: Windows
  • Release: 9.0.1
  • Role: Developer
  • Tags:
  • Replies: 0

Hi All,

I'm working on a XPages Documentation Wiki copy,
and I'm trying to to convert all the BASE64 images that are pasted to the rich-text editor to attached-image files so they won't reduce performances
(for now, base64-images copy is disabled via cliend-side js).

THIS IS HOW I DO IT:
In the xPage "all properties" tab, under "data" & the relevant document, there is a script for "postSaveDocument" event.
There I'm calling a SSJS function called "handleBase64" & send to it as params - the rich-text field name & the xspDocument (where the attachments should go).
This function (code below) is:
1. Getting the HTML code from the relevant entity
2. Making the MIME Entity "multipart" if its not.
3. Iterating over the HTML code - for every occurrence of an HTML IMAGE TAG which is a base64 -
the BASE64 code itself is being sent to a JAVA library (code also below) that CREATES A CHILD ENTITY for the MIME PART, and save the image file in it.
4. Changing the old HTML CODE - replacing the BASE64 image tags' src attribute with a "cid:[AttachmentName]".
5. SAVE.

THE PROBLEM IS AT STAGE 2 !
When adding base64 images to a document which it's rich-text field is not MULTIPART MIME yet,
as you can see, I'm trying to change it to a MULTIPART MIME ENTITY,
because otherwise the "CreateChildEntity" method in the JAVA library will crash.
When I'm "turning" the MIME ENTITY from TEXT to MULTIPART - everything looks well on the first edit\save -
The HTML referring to the attachments instead of the base64 data, and those are saved as attachments in the rich-text field.
BUT when I'm editing & saving again with new base64 images - they won't be shown.

I pretty sure that there is something wrong with my code that should turn the MIME to MULTIPART, so i marked it with BOLD RED.

SSJS CODE THAT RUNS ON "postSaveDocument" EVENT:
function handleBase64 (richTextFieldName, xspDocument) {
    var targetDoc = xspDocument.getDocument(true);
    //GETTING THE HTML CODE FROM THE RELEVANT ENTITY
    var htmlEntityFound = false;
    var allEnteties = targetDoc.getMIMEEntity(richTextFieldName);
    if (allEnteties.getContentType() == "text") {
        var htmlChildEntity = currChildEntity;
        var html = allEnteties.getContentAsText();
        htmlEntityFound = true;
    } else if (allEnteties.getContentType() == "multipart") {
        var currChildEntity:NotesMIMEEntity = allEnteties.getFirstChildEntity();
        while (currChildEntity && currChildEntity.getContentType() != "text") {
            currChildEntity = currChildEntity.getNextEntity();
        }
        if (currChildEntity) {
            var html = currChildEntity.getContentAsText();
            htmlEntityFound = true;
        }
    }
    //CHECK IF THERE ARE BASE64 IMAGES. IF NOT - DONE!
    if (htmlEntityFound && html.match(/<img(.)+?src="data:image\/(...|....);base64,(.)+?>/i) != null) {
        //FIND & CONVERT BASE64 IMAGES!
        session.setConvertMIME(false);
        var htmlWithoutBase64Tags = html.split(/<img(.)+?src="data:image\/(...|....);base64,(.)+?>/i);
        var htmlConvertedImages = [];
        var htmlConvertedImagesExtraAttributes = [];
        htmlNextImageTag = html.match(/<img(.)+?src="data:image\/(...|....);base64,(.)+?>/i);
        if (htmlNextImageTag != null) {
            htmlNextImageTag = htmlNextImageTag[0];
        }
        //CHECK IF THERE IS A MULTIPART ENTITY. IF NOT - CREATE ONE!
        if (targetDoc.getMIMEEntity(richTextFieldName).getContentType() != 'multipart') {
            var ent2 = targetDoc.getMIMEEntity(richTextFieldName);
            var parent = ent2.createParentEntity();
            var child = parent.createChildEntity();
        }

        var j = 0;
        //ITERATE THROUGH BASE64 IMAGES TAGS AND TAKE CARE OF THEM
        while (htmlNextImageTag) {
            var base64str = htmlNextImageTag.substr(htmlNextImageTag.search(/src="data:image\/(...|....);base64,/i) + 27, htmlNextImageTag.length);
            if (base64str.charAt(0) == ",") base64str = base64str.substr(1, base64str.length);
            base64str = base64str.substr(0, base64str.indexOf('"'));
            var fileExtension = htmlNextImageTag.substr(htmlNextImageTag.search(/src="data:image\//i) + 16, htmlNextImageTag.length);
            fileExtension = fileExtension.substr(0, fileExtension.search(/;base64,/i));
            var extraAttributes = htmlNextImageTag.split("<img").join(" ").split(/src="data:image\/(...|....);base64,(.)+?"/i).join(" ").split(/(\/>|>)/).join(" ");
            if (extraAttributes == null || extraAttributes == "") {
                extraAttributes = ' ';
            }
            //FIND NUMBER OF BASE64 CONVERTED IMAGE
            var filename = "attachedconvertedbase64image";
            var emptyFileNameFound = false;
            while (!emptyFileNameFound && j < 5000) {
                emptyFileNameFound = true;
                var allEnteties = targetDoc.getMIMEEntity(richTextFieldName);
                if (allEnteties != null) {
                    var currChildEntity:NotesMIMEEntity = allEnteties.getFirstChildEntity();
                    while (currChildEntity && emptyFileNameFound) {
                        if (currChildEntity.getHeaders().indexOf("attachedconvertedbase64image" + j) != -1) {
                            emptyFileNameFound = false;
                        }
                        currChildEntity = currChildEntity.getNextEntity();
                    }
                }
                if (emptyFileNameFound) {
                    filename = "attachedconvertedbase64image" + j;
                }
                j++;
            }
            //ACTUAL SAVE - BY EXTERNAL JAVA LIBRARY
            var saveImage:com.dalet.xpages.SaveBase64Image = new com.dalet.xpages.SaveBase64Image(targetDoc, base64str, filename, fileExtension, richTextFieldName);
            saveImage.process();

            //REMOVE FROM LONG OLD-HTML STRING, SAVE NAME FOR LATER AND FIND NEXT BASE64-IMAGE-TAG
            html = html.replace(/<img(.)+?src="data:image\/(...|....);base64,(.)+?>/i, '');
            htmlConvertedImages.push(filename);
            htmlConvertedImagesExtraAttributes.push(extraAttributes);
            htmlNextImageTag = html.match(/<img(.)+?src="data:image\/(...|....);base64,(.)+?>/i);
            if (htmlNextImageTag != null) {
                htmlNextImageTag = htmlNextImageTag[0];
            }
        }
        //CREATE THE NEW HTML STRING & SAVE IT
        var newHtml = '';
        for (var k = 0; k < htmlWithoutBase64Tags.length - 1; k++) {
            newHtml += htmlWithoutBase64Tags[k] + '<img ' + htmlConvertedImagesExtraAttributes[k] + ' src=cid:' + htmlConvertedImages[k] + ' />';
        }
        newHtml += htmlWithoutBase64Tags[htmlWithoutBase64Tags.length - 1];
        //FIND THE HTML-TEXT ENTITY & REPLACE IT WITH NEW HTML
        var allEnteties = targetDoc.getMIMEEntity(richTextFieldName);
        var currChildEntity:NotesMIMEEntity = allEnteties.getFirstChildEntity();
        while (currChildEntity && currChildEntity.getHeaders().indexOf('text/html') == -1) {
            currChildEntity = currChildEntity.getNextEntity();
        }
        if (currChildEntity) {
            var stream = session.createStream();
            stream.writeText(newHtml);
            currChildEntity.setContentFromText(stream ,"text/html;charset=UTF-8", 1725);
            stream.close();
            targetDoc.closeMIMEEntities(true, 'content');
            targetDoc.save(true);
        }
        session.setConvertMIME(true);
    }
    return true;
}

JAVA BASE64 CONVERSION LIBRARY:
import java.io.ByteArrayInputStream;
import javax.xml.bind.DatatypeConverter;
import lotus.domino.Document;
import lotus.domino.MIMEEntity;
import lotus.domino.MIMEHeader;
import lotus.domino.Session;
import lotus.domino.Stream;
import com.ibm.xsp.model.domino.DominoUtils;

public class SaveBase64Image {
    Document targetDoc;
    String b64String;
    String currFileName;
    String richTextFieldName;
    String fileExtension;

    public SaveBase64Image(Document targetDoc, String imageBase64Str, String filename, String fileExtension, String richTextFieldName) {
        this.targetDoc = targetDoc;
        this.b64String = imageBase64Str;
        this.currFileName = filename;
        this.richTextFieldName = richTextFieldName;
        this.fileExtension = fileExtension;
    }

    public void process() {
        if (b64String.trim().equals("")) {
            System.out.println("Error in SaveBase64Image.process() - b64str Empty");
        }
        
        try {
            // Convert the Base64 string to binary
            byte[] imgBytes = DatatypeConverter.parseBase64Binary(b64String);
            
            Session s = DominoUtils.getCurrentSession();
            s.setConvertMime(false);

            // Create MIME to store the image
            MIMEEntity richtext = targetDoc.getMIMEEntity(this.richTextFieldName);
            MIMEEntity child = richtext.createChildEntity();
            MIMEHeader header = child.createHeader("Content-Disposition");
            header.setHeaderVal("inline; filename=\"" + this.currFileName + "." + this.fileExtension + "\"" + ";");
            MIMEHeader header2 = child.createHeader("Content-ID");
            header2.setHeaderVal("<" + this.currFileName + ">");
            
            // Add the image to MIME via stream
            Stream stream = s.createStream();
            stream.setContents(new ByteArrayInputStream(imgBytes));
            child.setContentFromBytes(stream, "image/" + this.fileExtension, MIMEEntity.ENC_IDENTITY_BINARY);
            stream.close();
            
            s.setConvertMime(true);
        } catch (Exception e) {
            System.out.println("Error in SaveBase64Image.process()");
            e.printStackTrace();
        }
    }
}


FORUM PLAN UPDATE
Date revision: This forum will remain open to new posts and responses until December 1, 2018. (After that date, you will still be able to view and search the forum.) Also, we're taking a second look at the best place to host future conversation. For now, keep using this forum, and stay tuned for more news.