7

I have a solution for copying rich text content from one document to MIME in another document. See http://per.lausten.dk/blog/2012/12/xpages-dynamically-updating-rich-text-content-in-a-ckeditor.html. I use this in an application as a way for the user to insert content templates in a new document and have the content appear on-the-fly in the CKEditor.

The problem is that inline images are not included in the copying - only a reference to temporary storage of the images. This means that the images are only visible for the current user in the current session. So not very useful.

How can I include images?

Update October 4, 2013: I'm still looking for a solution to this.

Per Henrik Lausten
  • 21,331
  • 3
  • 29
  • 76

3 Answers3

5

I finally got it work. It was much more simple and did not even involve MIME. The trick was to modify the image tags in the working HTML to include the base64 encoded image so that the src tag could use this format (here shown with a gif as example):

src="data:image/gif;base64,<base64 encoded image>"

I already had the code necessary to get the HTML from the rich text field (see my blog post already mentioned in my question). So all I needed was to replace the image src tags with the correct src format including the base64 encoded image.

The following code gets the HTML and goes through each of the included images and modifies the src tag:

String html = this.document.getValue(fieldName).toString();
if (null != html) {
    final List<FileRowData> fileRowDataList = document.getEmbeddedImagesList(fieldName);
    if (null != fileRowDataList) {
        final Matcher matcher = imgRegExp.matcher(html);
        while (matcher.find()) {
            String src = matcher.group();
            final String srcToken = "src=\"";
            final int x = src.indexOf(srcToken);
            final int y = src.indexOf("\"", x + srcToken.length());
            final String srcText = src.substring(x + srcToken.length(), y);
            for (FileRowData fileRowData : fileRowDataList) {
                final String srcImage = fileRowData.getHref();
                final String cidImage = ((AttachmentValueHolder) fileRowData).getCID();
                final String typeImage = ((AttachmentValueHolder) fileRowData).getType();
                final String persistentName = ((AttachmentValueHolder) fileRowData).getPersistentName();

                // Add base 64 image inline (src="data:image/gif;base64,<name>")
                if (srcText.endsWith(srcImage)) {
                    final String newSrc = src.replace(srcText, "data:" + typeImage + ";base64," + getBase64(persistentName));
                    html = html.replace(src, newSrc);
                }
            }
        }
    }
}

Here is the getBase64() method that base64 encodes an image:

private String getBase64(final String fileName) {
    String returnText = "";
    try {
        BASE64Encoder base64Enc = new BASE64Encoder();
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        base64Enc.encode(this.getEmbeddedImageStream(fileName), output);
        returnText = output.toString();
    } catch (NotesException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }

    return returnText;
}

Some of the code is from the emailBean by Tony McGuckin.

Per Henrik Lausten
  • 21,331
  • 3
  • 29
  • 76
  • Cool indeed. Did my use of inline images inspire you? What would be interesting to compare the regex with the Jericho parser performance and/ or maintainability – stwissel Oct 12 '13 at 06:38
2

Can you get a handle to the inline image by the DominoDocument.AttachmentValueHolder, see http://public.dhe.ibm.com/software/dw/lotus/Domino-Designer/JavaDocs/XPagesExtAPI/8.5.2/com/ibm/xsp/model/domino/wrapped/DominoDocument.AttachmentValueHolder.html

I blogged about attachments inside notes documents, see http://www.domino-weblog.nl/weblogs/Domino_Blog.nsf/dx/xpages-tip-get-easily-access-to-your-attachments-in-java.htm

  • 1
    I just discovered the getBodyHTML method in the emailBean by Tony McGuckin at http://openntf.org/XSnippets.nsf/snippet.xsp?id=emailbean-send-dominodocument-html-emails-cw-embedded-images-attachments-custom-headerfooter. It looks interesting. It uses AttachmentValueHolder too – Per Henrik Lausten Feb 27 '13 at 20:24
1

Gruesome hack (you need to sort out authentication and server names)

The SSJS (getting the source from a view)

var unid = curRow.getUniversalID();
var body = getComponent("body1");
var magic = new demo.HTMLMagic();
magic.doMagic(database, unid, body);

The Java

package demo;


import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.HttpVersion;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;

import net.htmlparser.jericho.Attribute;
import net.htmlparser.jericho.Attributes;
import net.htmlparser.jericho.HTMLElementName;
import net.htmlparser.jericho.OutputDocument;
import net.htmlparser.jericho.Source;
import net.htmlparser.jericho.StartTag;
import lotus.domino.Database;
import lotus.domino.NotesException;

import com.ibm.misc.BASE64Encoder;
import com.ibm.xsp.component.xp.XspInputRichText;
import com.ibm.xsp.http.IMimeMultipart;
import com.ibm.xsp.model.domino.wrapped.DominoRichTextItem;

public class HTMLMagic {

private HttpClient          httpClient  = null;
private HttpHost            httpHost    = null;
//TODO: that needs to be resolved smarter
private static final String HOSTNAME    = "localhost";

public void doMagic(final Database database, final String unid, final XspInputRichText body) throws NotesException,
        ClientProtocolException, IOException {
    final String docURL = "http://" + HOSTNAME + "/__" + database.getReplicaID() + ".nsf/0/" + unid + "/Body?OpenField";
    final String fixedHTML = this.fixHTML(docURL);
    IMimeMultipart result = DominoRichTextItem.convertToMime("-- copied text--<br />" + fixedHTML);
    body.setValue(result);
}

private String fixHTML(final String rawHTMLstring) throws ClientProtocolException, IOException {
    HttpHost target = this.getHttpHost();
    HttpClient client = this.getHttpClient();
    HttpGet get = new HttpGet(rawHTMLstring);
    HttpResponse response = client.execute(target, get);
    InputStream data = response.getEntity().getContent();
    Source rawHTML = new Source(data);
    OutputDocument outputDocument = new OutputDocument(rawHTML);
    StringBuilder sb = new StringBuilder();
    String tagName = HTMLElementName.IMG;
    String attName = "src";
    List<StartTag> links = rawHTML.getAllStartTags(tagName);

    for (StartTag onelink : links) {
        String href = onelink.getAttributeValue(attName);
        if (href != null) {
            String replace = this.urltoData(href);
            if (replace != null) {
                sb.setLength(0);
                sb.append("<");
                sb.append(tagName);
                sb.append(" ");
                sb.append(attName);
                sb.append("=\"");
                sb.append(replace);
                sb.append("\"");
                Attributes atts = onelink.getAttributes();
                if (!atts.isEmpty()) {
                    for (int i = 0; i < atts.size(); i++) {
                        Attribute att = atts.get(i);
                        if (!att.getName().equals(attName)) {
                            sb.append(" ");
                            sb.append(att.getName());
                            sb.append("=\"");
                            sb.append(att.getValue());
                            sb.append("\" ");
                        }
                    }
                }
                sb.append(">");
                outputDocument.replace(onelink, sb.toString());
            }
        }
    }
    return outputDocument.toString();
}

private HttpClient getHttpClient() {

    if (this.httpClient == null) {

        // general setup
        SchemeRegistry supportedSchemes = new SchemeRegistry();

        // Register the "http" protocol scheme, it is required
        // by the default operator to look up socket factories.
        supportedSchemes.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));

        // prepare parameters
        HttpParams params = new BasicHttpParams();
        HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
        HttpProtocolParams.setContentCharset(params, "UTF-8");
        HttpProtocolParams.setUseExpectContinue(params, true);

        ClientConnectionManager connMgr = new ThreadSafeClientConnManager(params, supportedSchemes);
        this.httpClient = new DefaultHttpClient(connMgr, params);
    }
    return this.httpClient;
}

private HttpHost getHttpHost() {
    if (this.httpHost == null) {

        this.httpHost = new HttpHost(HOSTNAME, 80, "http");
    }
    return this.httpHost;
}

private String urltoData(final String href) throws ClientProtocolException, IOException {
    StringBuilder sb = new StringBuilder();
    sb.append("data:image/");
    sb.append(href.substring(href.lastIndexOf("FieldElemFormat=") + 1));
    sb.append(";base64,");

    // Here go the Image data
    HttpHost target = this.getHttpHost();
    HttpClient client = this.getHttpClient();
    HttpGet get = new HttpGet(href);
    HttpResponse response = client.execute(target, get);
    InputStream data = response.getEntity().getContent();

    BASE64Encoder encoder = new BASE64Encoder();
    OutputStream output = new ByteArrayOutputStream();
    encoder.encode(data, output);
    sb.append(output.toString());
    output.close();

    return sb.toString();
}

}

Would be curious if that works for you. The Notes client can't show inline HTML images

stwissel
  • 20,110
  • 6
  • 54
  • 101
  • Of course: instead of squeezing the image data completely inline, you can go an turn them into proper MIME Parts. In general the same approach. You also could pull the DXL instead of using HTTP to get to the Base64 version of the image. – stwissel Oct 08 '13 at 14:55
  • Since this fetches the source document as a HTTP request in the backend, it requires some more work in order to work "seamlessly" in an environment where the source document is access protected. Also, I believe that links to inline images should use the following format: – Per Henrik Lausten Oct 11 '13 at 08:36
  • Yes. You need to generate the token from the HTTP session. Alternsate: DXL delivers the images encoded too – stwissel Oct 11 '13 at 09:01
  • I am having great progress on using MIME from the backend document and not using HTTP. I can now output MIME including Base64 encoded images but can not get CKEditor to accept the images. So it was not necessary to use your hack. – Per Henrik Lausten Oct 11 '13 at 17:13
  • Good to hear. How did you get the stuff out? For the ckeditor: if mime-parts doesn't work (the CID: ) use parts of my hack using inline images (which is HTML, not mime) - that worked – stwissel Oct 12 '13 at 04:37
  • I was already using a wrapDocument method to get a NotesXspDocument representation of the backend document (see my blog post mentioned in the question). With the NotesXspDocument I use getValue() to get the HTML of the rich text field. The trick was then to extract the images, base64 encode them and add them as inline images (see my answer). So in the end I did not have to use MIME. But I learned a lot about MIME in the process :-) – Per Henrik Lausten Oct 12 '13 at 05:48