All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ch.dvbern.lib.doctemplate.rtf.RTFMergeEngine Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2017 DV Bern AG
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * limitations under the License.
 */
package ch.dvbern.lib.doctemplate.rtf;

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.Reader;
import java.math.BigDecimal;
import java.util.Date;
import java.util.Map;
import java.util.Stack;
import java.util.StringTokenizer;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import ch.dvbern.lib.doctemplate.common.BasicMergeElement;
import ch.dvbern.lib.doctemplate.common.ConditionMergeElement;
import ch.dvbern.lib.doctemplate.common.DocTemplateException;
import ch.dvbern.lib.doctemplate.common.Image;
import ch.dvbern.lib.doctemplate.common.IterationMergeElement;
import ch.dvbern.lib.doctemplate.common.MergeContext;
import ch.dvbern.lib.doctemplate.common.MergeSource;
import ch.dvbern.lib.doctemplate.common.StaticMergeElement;
import ch.dvbern.lib.doctemplate.util.FormatHelper;
import ch.dvbern.lib.doctemplate.util.LdtConstants;

import net.sourceforge.rtf.RTFTemplate;
import net.sourceforge.rtf.document.RTFDocument;
import net.sourceforge.rtf.document.RTFElement;
import net.sourceforge.rtf.document.RTFEndBookmark;
import net.sourceforge.rtf.document.RTFField;
import net.sourceforge.rtf.document.RTFStartBookmark;
import net.sourceforge.rtf.helper.RTFTemplateBuilder;

/**
 * Merged eine RTF-Vorlage mit Informationen, die Aufgrund der Bezeichnungen, die innerhalb der Vorlage als MERGE-Fields
 * enthalten sind zusammen. Die Komponente nutzt RTFTemplate (http://rtftemplate.sourceforge.net) als Basistechnologie.
 * Der Code in RTFTemplate ist in der Version 1.0.1-b13 nicht Thread-Safe!!!
 */
public class RTFMergeEngine {

	private static final String CONDITION_BEGIN = "IF_";
	private static final String CONDITION_END = "ENDIF_";
	private static final String ITERATION_BEGIN = "WHILE_";
	private static final String ITERATION_END = "ENDWHILE_";
	private static final String SORTFIELD_PREFIX = "SORT_";
	private static final String ALTERNATE_SUFFIX = "_ALT";
	private static final String FORMAT_SUFFIX = "_FMT";

	private static final String DOCUMENT_CONTENT_BEGIN = "{\\*\\bkmkend DOCUMENT_CONTENT_BEGIN}";
	private static final String DOCUMENT_CONTENT_END = "{\\*\\bkmkstart DOCUMENT_CONTENT_END}";

	private static final String syncPoint = new String("RTFMergeEngine::syncPoint");

	/** Logger */
	private static final Log log = LogFactory.getLog(RTFMergeEngine.class);

	private final String name;
	private Stack parseStack;
	private Map keyTranslationTable = null;

	/**
	 * Initialisierung der Engine mit einem kennzeichnenden Namen.
	 *
	 * @param name Kennzeichnung
	 */
	public RTFMergeEngine(String name) {

		this.name = name;
	}

	/**
	 * Extrahiert den Inhalt eines Dokuments (ohne Header und Footer), damit dieser beispielsweise in ein Hauptdokument
	 * eingefuegt werden kann.
	 *
	 * @param originalContent Input-Dokument
	 * @return Input ohne Header und Footer (Inhalt zwischen den BEGIN/END-Bookmarks)
	 */
	public String extractDocumentContent(String originalContent) {

		StringBuffer sb = new StringBuffer(new String(originalContent));
		int pos = sb.indexOf(DOCUMENT_CONTENT_BEGIN);
		if (pos > 0) {
			sb.delete(0, pos + DOCUMENT_CONTENT_BEGIN.length());
		}
		pos = sb.indexOf(DOCUMENT_CONTENT_END);
		if (pos > 0) {
			sb.setLength(pos);
		}
		return sb.toString();
	}

	/**
	 * Fuegt Subdokumente aus includesMap in das Hauptdokument holderTemplate ein.
	 *
	 * @param holderTemplate Hauptdokument
	 * @param includesMap Subdokumente
	 * @return zusammengefuegtes Dokument
	 */
	public String includeSubDocuments(String holderTemplate, Map includesMap) {

		StringBuffer sb = new StringBuffer(holderTemplate);
		String INCLUDE_BEGIN = "{\\*\\bkmkstart DOCUMENT_INCLUDE_";
		for (int start = sb.indexOf(INCLUDE_BEGIN); start > 0; start = sb.indexOf(INCLUDE_BEGIN)) {
			int end = sb.indexOf("}", start);
			String name = sb.substring(start + INCLUDE_BEGIN.length(), end);

			String INCLUDE_END = "{\\*\\bkmkend DOCUMENT_INCLUDE_" + name + "}";
			end = sb.indexOf(INCLUDE_END) + INCLUDE_END.length();
			sb.delete(start, end);

			StringBuffer include = includesMap.get(name);
			if (include != null) {
				sb.insert(start, include.toString());
			}
		}
		return sb.toString();
	}

	/**
	 * Merged aus der Vorlage reader ein Dokument und gibt dieses als byte[] zurueck. Die Platzhalter
	 * innerhalb der Vorlage werden mit Hilfe der mergeSource ermittelt und abgefuellt.
	 *
	 * @param reader Bezugsquelle fuer die Vorlage
	 * @param mergeSource Quelle fuer die Informationen zum abfuellen des Templates
	 * @param keyTranslationTable Uebersetzung von Keys, damit Einschraenkungen von Word umgangen werden koennen
	 * @return Ergebnisdokument
	 * @throws DocTemplateException Gatherer Fehler der eine Benutzerfehlermeldung erzeugen sollen
	 */
	public byte[] getDocument(Reader reader, MergeSource mergeSource, Map keyTranslationTable) throws DocTemplateException {

		this.keyTranslationTable = keyTranslationTable;
		return getDocument(reader, mergeSource);
	}

	/**
	 * Merged aus der Vorlage reader ein Dokument und gibt dieses als byte[] zurueck. Die Platzhalter
	 * innerhalb der Vorlage werden mit Hilfe der mergeSource ermittelt und abgefuellt.
	 *
	 * @param reader Bezugsquelle fuer die Vorlage
	 * @param mergeSource Quelle fuer die Informationen zum abfuellen des Templates
	 * @return Ergebnisdokument
	 * @throws DocTemplateException Gatherer Fehler der eine Benutzerfehlermeldung erzeugen sollen
	 */
	public byte[] getDocument(Reader reader, MergeSource mergeSource) throws DocTemplateException {

		synchronized (syncPoint) {
			try {
				RTFTemplate rtfTemplate = RTFTemplateBuilder.newRTFTemplateBuilder().newRTFTemplate();
				rtfTemplate.setTemplate(reader);

				// Template mit MergeSourcen aufbereiten
				log.debug(name + ": RTF Template in Substrukturen transformieren");
				RTFDocument rtfDoc = rtfTemplate.transform();
				BasicMergeElement bme = new BasicMergeElement();
				parseStack = new Stack();
				parseStack.push(bme);
				log.debug(name + ": RTF Template parsen");
				parseTemplate(rtfDoc);
				if (parseStack.size() > 1) {
					throw new DocTemplateException("error.rtftemplate.invalid.structure");
				}
				return bme.getContent(new MergeContext(mergeSource), mergeSource).getBytes();
			} catch (DocTemplateException sfe) {
				throw sfe;
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
		}
	}

	private void parseTemplate(RTFElement rtfElement) throws DocTemplateException {

		for (Object o : rtfElement.getElementList()) {
			if (o instanceof RTFElement) {
				if (o instanceof RTFField) {
					String key = ((RTFField) o).getName();
					String rtfCode = ((RTFField) o).getRTFContentOfSimpleElement();
					if (key == null || key.startsWith("$")) {
						StaticMergeElement sme = new StaticMergeElement(rtfCode);
						parseStack.peek().addMergeElement(sme);
					} else {
						FieldMergeElement fme = new FieldMergeElement(translate(key), rtfCode);
						parseStack.peek().addMergeElement(fme);
					}
				} else if (o instanceof RTFStartBookmark) {
					RTFStartBookmark bm = (RTFStartBookmark) o;
					String bmName = bm.getName();
					if (bmName.startsWith(CONDITION_BEGIN)) {
						String key = bmName.substring(CONDITION_BEGIN.length());
						ConditionMergeElement cme = new ConditionMergeElement(name, translate(key));
						cme.initFuerRtf();
						parseStack.peek().addMergeElement(cme);
						parseStack.push(cme);
					} else if (bmName.startsWith(ITERATION_BEGIN)) {
						String key = bmName.substring(ITERATION_BEGIN.length());
						IterationMergeElement ime = new IterationMergeElement(name, translate(key));
						ime.initFuerRtf();
						parseStack.peek().addMergeElement(ime);
						parseStack.push(ime);
					} else if (bmName.startsWith(SORTFIELD_PREFIX)) {
						String key = bmName.substring(SORTFIELD_PREFIX.length());
						// mehrere gleiche Textmarken mit ALT-Suffix
						// intern ohne ALT-Suffix anwenden
						int altPos = key.indexOf(ALTERNATE_SUFFIX);
						if (altPos > 0) {
							key = key.substring(0, altPos);
						}
						Object ime = parseStack.peek();
						if (ime instanceof IterationMergeElement) {
							((IterationMergeElement) ime).addSortFieldKey(key);
						} else {
							log.warn("invalid structure: no IterationMergeElement on parse stack");
						}
					} else if (!bmName.startsWith(CONDITION_END) && !bmName.startsWith(ITERATION_END)) {
						String s = bm.getRTFContentOfSimpleElement();
						StaticMergeElement sme = new StaticMergeElement(s);
						parseStack.peek().addMergeElement(sme);
					}
				} else if (o instanceof RTFEndBookmark) {
					RTFEndBookmark bm = (RTFEndBookmark) o;
					String bmName = bm.getName();
					if (bmName.startsWith(CONDITION_END) || bmName.startsWith(ITERATION_END)) {
						if (parseStack.size() > 1) {
							parseStack.pop();
						} else {
							throw new DocTemplateException("error.rtftemplate.invalid.structure");
						}
					} else if (!bmName.startsWith(CONDITION_BEGIN) && !bmName.startsWith(ITERATION_BEGIN) && !bmName.startsWith(SORTFIELD_PREFIX)) {
						String s = bm.getRTFContentOfSimpleElement();
						StaticMergeElement sme = new StaticMergeElement(s);
						parseStack.peek().addMergeElement(sme);
					}
				} else {
					parseTemplate((RTFElement) o);
				}
			} else {
				String s = o.toString();
				StaticMergeElement sme = new StaticMergeElement(s);
				parseStack.peek().addMergeElement(sme);
			}
		}
	}

	private String translate(String key) {

		String result = key;
		if (keyTranslationTable != null) {
			for (Map.Entry entry : keyTranslationTable.entrySet()) {
				result = result.replace(entry.getKey(), entry.getValue());
			}
		}
		return result;
	}

	private class FieldMergeElement extends BasicMergeElement {

		private final String key;
		private final String rtfCode;

		/**
		 * Konstruktor eines MergeElements, dessen Ausgabe dynamisch ermittelt wird
		 *
		 * @param key Identifikation des Inhaltes, der fuer die Ausgabe ermittelt werden soll
		 * @param rtfCode RTF Code mit dem original Merge Field
		 */
		public FieldMergeElement(String key, String rtfCode) {

			this.key = key;
			this.rtfCode = rtfCode;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see
		 * ch.dvbern.rodos.merger.server.util.RTFMergeEngine.BasicMergeElement#getContent(ch.dvbern.rodos.merger.shared
		 * .MergeContext, ch.dvbern.rodos.merger.shared.MergeSource)
		 */
		@Override
		public String getContent(MergeContext ctx, MergeSource mergeSource) throws DocTemplateException {

			log.debug(name + ": evaluate template source with key " + key);

			// Format-Suffix aus key extrahieren
			String keyWithoutFormatSuffix = key, formatSuffix = null;
			int i = key.indexOf(FORMAT_SUFFIX);
			if (i > 0) {
				formatSuffix = key.substring(i + FORMAT_SUFFIX.length());
				keyWithoutFormatSuffix = key.substring(0, i);
			}

			Object data = mergeSource.getData(ctx, keyWithoutFormatSuffix);
			if (data instanceof Image) {
				return getImageAsRTF((Image) data, formatSuffix);
			}
			if (data != null) {
				String dataAsString = FormatHelper.getDataAsString(data, StringUtils.isEmpty(formatSuffix) ? getDefaultFormatter(data) : formatSuffix);
				dataAsString = convertRtfEncodings(dataAsString);
				int rtlchPos = rtfCode.indexOf("{\\rtlch\\fcs1");
				if (rtlchPos >= 0) {
					int mfldPos = rtfCode.indexOf(" MERGEFIELD", rtlchPos);
					if (mfldPos > 0 && mfldPos < rtfCode.indexOf("}", rtlchPos)) {
						StringBuffer replacement = new StringBuffer();
						replacement.append(rtfCode.substring(rtlchPos, mfldPos));
						replacement.append(dataAsString);
						replacement.append("}");
						return replacement.toString();
					}
				}
				return dataAsString;
			} else {
				log.warn(name + ": no template source with key " + key);
				return rtfCode;
			}
		}

		/**
		 * @param data
		 * @return
		 */
		protected String getDefaultFormatter(Object data) {

			String formatter = null;
			if (data == null) {
				return formatter;
			}
			if (data instanceof Number) {
				if (data instanceof Float || data instanceof Double || data instanceof BigDecimal) {
					formatter = getDefaultFloatFormat();
				} else {
					formatter = getDefaultIntFormat();
				}
			} else if (data instanceof Date) {
				formatter = getDefaultDateFormat();
			}
			return formatter;
		}

		/**
		 * @return
		 */
		protected String getDefaultDateFormat() {

			return LdtConstants.DEFAULT_DATE_FORMAT;
		}

		/**
		 * @return
		 */
		protected String getDefaultIntFormat() {

			return LdtConstants.DEFAULT_INT_FORMAT;
		}

		/**
		 * @return
		 */
		protected String getDefaultFloatFormat() {

			return LdtConstants.DEFAULT_FLOAT_FORMAT;
		}

		private String getImageAsRTF(Image image, String formatSuffix) throws DocTemplateException {

			StringBuilder result = new StringBuilder("{\\*\\shppict {\\pict");
			if (Image.Format.PNG == image.getFormat()) {
				result.append("\\pngblip");
			} else if (Image.Format.JPEG == image.getFormat()) {
				result.append("\\jpegblip");
			} else if (Image.Format.EMF == image.getFormat()) {
				result.append("\\emfblip");
			} else {
				throw new DocTemplateException("unknown image format: " + image.getFormat());
			}
			long w = image.getWidth(), h = image.getHeight();
			try {
				if (formatSuffix != null && formatSuffix.length() > 0) {
					StringTokenizer st = new StringTokenizer(formatSuffix, "_");
					if (st.hasMoreTokens()) {
						w = Integer.parseInt(st.nextToken());
					}
					if (st.hasMoreTokens()) {
						h = Integer.parseInt(st.nextToken());
					}
				}
				result.append("\\picw").append(w).append("\\pich").append(h);
				result.append("\\picwgoal").append(w * 15).append("\\pichgoal").append(h * 15);
				result.append(" ");
				ByteArrayInputStream bais = new ByteArrayInputStream(image.getBytes());
				DataInputStream dis = new DataInputStream(bais);
				while (dis.available() > 0) {
					String s = Integer.toHexString(dis.read());
					if (s.length() % 2 != 0) {
						result.append("0");
					}
					result.append(s);
				}
			} catch (IOException ioe) {
				log.warn("error reading image: " + formatSuffix, ioe);
			} catch (NumberFormatException nfe) {
				log.warn("invalid image format suffix: " + formatSuffix, nfe);
			}
			result.append("}}");
			return result.toString();
		}

		private String convertRtfEncodings(String data) {

			// special character encodings
			StringBuffer sb = new StringBuffer();
			int length = data.length();
			for (int i = 0; i < length; i++) {
				char c = data.charAt(i);
				if (c == '\n') {
					sb.append("\\line "); // LF einfuegen
				} else if (c == '\r') {
					sb.append(""); // CRs NICHT einfuegen
				} else if (c > 0xFF || c < 0x00) {
					sb.append("\\u");
					sb.append(Short.toString((short) c).toCharArray());
					sb.append("?");
				} else if (c >= 0x80 || c < 0x20 || c == 0x5C || c == 0x7B || c == 0x7D) {
					sb.append("\\'");
					sb.append(Integer.toHexString(c));
				} else {
					sb.append(c);
				}
			}
			return sb.toString();
		}
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy