Printing a Form from ODK Collect using XSLT

Just some proof :grin:

Screenshot_20200214-164902 (1)

Not a very elegant solution as we still need to update the XSLT for each new Form Template that we want to print out, but it serves our needs at the minute!

If anyone is interested here is the main PrintFormInstanceActivity code:

package org.odk.collect.android.activities;

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.print.PrintAttributes;
import android.print.PrintDocumentAdapter;
import android.print.PrintJob;
import android.print.PrintManager;
import android.util.Log;
import android.view.Window;
import android.webkit.WebView;
import android.webkit.WebViewClient;


import org.apache.commons.io.FileUtils;
import org.odk.collect.android.R;
import org.odk.collect.android.application.Collect;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;

import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

public class PrintFormInstanceActivity extends Activity {
    public static final String TAG = "YOUR-TAG-NAME";
    private WebView mWebView;
    /**
     * Called when the activity is first created.
     */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        getWindow().requestFeature(Window.FEATURE_PROGRESS);

        WebView webview = new WebView(this);
        setContentView(webview);

        //Reading XSLT
        String strXSLT = GetStyleSheet(R.raw.xsltfile);

        //Reading XML

        File InstanceXmlFile = Collect.getInstance().getFormController().getInstanceFile();

        String strXML = null;
        try {
            strXML = FileUtils.readFileToString(InstanceXmlFile, "utf-8");
        } catch (IOException e) {
            e.printStackTrace();
        }


        //Transform ...
        String html = StaticTransform(strXSLT, strXML);


        //Loading the above transformed XSLT in to Webview...
        webview.loadData(html, "text/html", null);


        WebView webView = new WebView(this);
        webView.setWebViewClient(new WebViewClient() {

            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                return false;
            }

            @Override
            public void onPageFinished(WebView view, String url) {
                Log.i(TAG, "page finished loading " + url);
                createWebPrintJob(view);
                mWebView = null;
            }
        });

        webView.loadDataWithBaseURL(null, html, "text/HTML", "UTF-8", null);

        mWebView = webView;


    }

    /** Google's WebView print code **/

    private void createWebPrintJob(WebView webView) {

        // Get a PrintManager instance
        PrintManager printManager = (PrintManager) this
                .getSystemService(Context.PRINT_SERVICE);

        String jobName = getString(R.string.app_name) + " Document";

        // Get a print adapter instance
        PrintDocumentAdapter printAdapter = webView.createPrintDocumentAdapter(jobName);

        // Set default page size to A4
        PrintAttributes.Builder builder = new PrintAttributes.Builder();
        builder.setMediaSize( PrintAttributes.MediaSize.ISO_A4);

        // Create a print job with name and adapter instance
        PrintJob printJob = printManager.print(jobName, printAdapter, builder.build());


        // Save the job object for later status checking
        // printJobs.add(printJob);
    }


    /**
     * Transform XML and XSLT to HTML string
     **/
    public static String StaticTransform(String strXsl, String strXml) {
        String html = "";

        try {

            InputStream ds = null;
            ds = new ByteArrayInputStream(strXml.getBytes("UTF-8"));

            Source xmlSource = new StreamSource(ds);

            InputStream xs = new ByteArrayInputStream(strXsl.getBytes("UTF-8"));
            Source xsltSource = new StreamSource(xs);

            StringWriter writer = new StringWriter();
            Result result = new StreamResult(writer);
            TransformerFactory tFactory = TransformerFactory.newInstance();
            Transformer transformer = tFactory.newTransformer(xsltSource);
            transformer.transform(xmlSource, result);

            html = writer.toString();

            ds.close();
            xs.close();

            xmlSource = null;
            xsltSource = null;

        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (TransformerConfigurationException e) {
            e.printStackTrace();
        } catch (TransformerFactoryConfigurationError e) {
            e.printStackTrace();
        } catch (TransformerException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }

        return html;
    }

    /**
     * Read XSLT file from res/raw...
     **/
    private String GetStyleSheet(int fileId) {
        String strXsl = null;

        InputStream raw = getResources().openRawResource(fileId);
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        int size = 0;
        // Read the entire resource into a local byte buffer.
        byte[] buffer = new byte[1024];
        try {
            while ((size = raw.read(buffer, 0, 1024)) >= 0) {
                outputStream.write(buffer, 0, size);
            }
            raw.close();

            strXsl = outputStream.toString();

            Log.v("Log", "xsl ==> " + strXsl);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return strXsl;

    }



}

Was a bit hesitant about sharing the code as I know it's pretty horrendous, but it's my first shot at coding so I guess I don't even know how bad it really is :sweat_smile:

P.s. there maaay be some memory leak issues. I tried to narrow it down with the profiler but all I could see was something related to the GNSS stuff, so not sure if I did something or it's always like that?

2 Likes