/******************************************************************************* * Copyright (c) 2009-2014, A. Kaufmann, Niklaus Giger and Elexis * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * A. Kaufmann - initial implementation * Niklaus Giger - Ported to Elexis 3.0, some bug fixes * *******************************************************************************/ /** * This text-plugin can work under Linux/MacoSX/Windows with any editor which is able to modify in * the OpenDocument Format. It's primary use is for LibreOffice under Windows and Linux. * <p> * The user documentation can be found at * <p> * http://wiki.elexis.info/Com.hilotec.elexis.opendocument.feature.feature.group * <p> * <p> * The following features are implemented * <p> * * all normally supported formats for text (bold, italic) * <p> * * Tables in templates allow [] which contains a place holder {} for the formatting of each line. * This allows to configure the width of each column * <p> * * Create an underlined line for lines starting and ending with '_' * * <p> * * ODT documents are removed after saving their contents as extinfo in the corresponding Brief. * <p> * * A list of all currently opened documents is show in the view of the plugin. * <p> * * A helper script (open_odf.bat/sh) is used to launch the editor application. It may not return * before the editor application closed the document. It must return soon after the document is * closed or Elexis will wait forever. * <p> * * Known working templates can be found under https://github.com/hilotec/elexis-vorlagen * <p> * Known deficiencies: * <p> * * Some combinations of unreadable documents/double clicking an already open document may result * in an unspecified behaviour. * <p> * * Separate instances of Elexis can modify the same document. (This problem should probably be * fixed in elexis core). * <p> * * A user can save the document under another name. In this case Elexis is unaware of the changes * in that document and will ignore any modifications made there. * * @author Antoine Kaufmann & Niklaus Giger */ package com.hilotec.elexis.opendocument; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.layout.RowData; import org.eclipse.swt.layout.RowLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.FileDialog; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Listener; import org.odftoolkit.odfdom.OdfFileDom; import org.odftoolkit.odfdom.OdfXMLFactory; import org.odftoolkit.odfdom.doc.OdfDocument; import org.odftoolkit.odfdom.doc.OdfTextDocument; import org.odftoolkit.odfdom.doc.draw.OdfDrawFrame; import org.odftoolkit.odfdom.doc.draw.OdfDrawTextBox; import org.odftoolkit.odfdom.doc.office.OdfOfficeAutomaticStyles; import org.odftoolkit.odfdom.doc.office.OdfOfficeFontFaceDecls; import org.odftoolkit.odfdom.doc.office.OdfOfficeText; import org.odftoolkit.odfdom.doc.style.OdfStyle; import org.odftoolkit.odfdom.doc.style.OdfStyleBackgroundImage; import org.odftoolkit.odfdom.doc.style.OdfStyleColumns; import org.odftoolkit.odfdom.doc.style.OdfStyleFontFace; import org.odftoolkit.odfdom.doc.style.OdfStyleGraphicProperties; import org.odftoolkit.odfdom.doc.style.OdfStyleParagraphProperties; import org.odftoolkit.odfdom.doc.style.OdfStyleTableColumnProperties; import org.odftoolkit.odfdom.doc.style.OdfStyleTextProperties; import org.odftoolkit.odfdom.doc.text.OdfTextLineBreak; import org.odftoolkit.odfdom.doc.text.OdfTextParagraph; import org.odftoolkit.odfdom.doc.text.OdfTextTab; import org.odftoolkit.odfdom.dom.element.OdfStylableElement; import org.odftoolkit.odfdom.dom.element.draw.DrawFrameElement; import org.odftoolkit.odfdom.dom.element.draw.DrawTextBoxElement; import org.odftoolkit.odfdom.dom.element.style.StyleBackgroundImageElement; import org.odftoolkit.odfdom.dom.element.style.StyleColumnsElement; import org.odftoolkit.odfdom.dom.element.style.StyleFontFaceElement; import org.odftoolkit.odfdom.dom.element.style.StyleGraphicPropertiesElement; import org.odftoolkit.odfdom.dom.element.style.StyleParagraphPropertiesElement; import org.odftoolkit.odfdom.dom.element.style.StyleStyleElement; import org.odftoolkit.odfdom.dom.element.style.StyleTableColumnPropertiesElement; import org.odftoolkit.odfdom.dom.element.style.StyleTextPropertiesElement; import org.odftoolkit.odfdom.dom.element.table.TableTableCellElement; import org.odftoolkit.odfdom.dom.element.table.TableTableColumnElement; import org.odftoolkit.odfdom.dom.element.table.TableTableElement; import org.odftoolkit.odfdom.dom.element.table.TableTableRowElement; import org.odftoolkit.odfdom.dom.element.text.TextPElement; import org.odftoolkit.odfdom.dom.element.text.TextSpanElement; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.Text; import ch.elexis.core.data.activator.CoreHub; import ch.elexis.core.data.events.ElexisEventDispatcher; import ch.elexis.core.data.interfaces.text.ReplaceCallback; import ch.elexis.core.data.util.PlatformHelper; import ch.elexis.core.ui.UiDesk; import ch.elexis.core.ui.text.ITextPlugin; import ch.elexis.core.ui.util.SWTHelper; import ch.elexis.data.Patient; import ch.rgw.tools.StringTool; import com.hilotec.elexis.opendocument.Export.Exporter; public class TextPlugin implements ITextPlugin { /** Internal Representation of current style */ private class Style { final static int ALIGN = SWT.LEFT | SWT.CENTER | SWT.RIGHT; String font = null; public int flags; Float size = null; public void setStyle(int s){ flags = s; } public void clearAlign(){ flags &= (~ALIGN); } public void setAlign(int a){ clearAlign(); flags |= a; } public void setFont(String n, int f, float s){ font = n; flags = f; size = s; } private String label(){ MessageDigest m; try { String pass = "" + flags; if (font != null) pass += "_" + font; if (size != null) pass += "_" + size; m = MessageDigest.getInstance("MD5"); byte[] data = pass.getBytes(); m.update(data, 0, data.length); BigInteger i = new BigInteger(1, m.digest()); return String.format("%1$032X", i); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return null; } private void declareFont(String name) throws Exception{ OdfFileDom contentDom = odt.getContentDom(); XPath xpath = odt.getXPath(); // Declare font face OdfOfficeFontFaceDecls ffdec = (OdfOfficeFontFaceDecls) xpath.evaluate("//office:font-face-decls", contentDom, XPathConstants.NODE); OdfStyleFontFace fface = (OdfStyleFontFace) ffdec.getFirstChild(); NodeList nl = (NodeList) xpath.evaluate("//office:font-face-decls/style:font-face[@style:name='" + font + "']", contentDom, XPathConstants.NODESET); if (nl.getLength() == 0) { fface = (OdfStyleFontFace) OdfXMLFactory.newOdfElement(contentDom, StyleFontFaceElement.ELEMENT_NAME); fface.setStyleNameAttribute(font); fface.setSvgFontFamilyAttribute("'" + font + "'"); ffdec.appendChild(fface); } } private void createStyle(String sname, String family){ try { OdfFileDom contentDom = odt.getContentDom(); XPath xpath = odt.getXPath(); // Create Style NodeList nl = (NodeList) xpath.evaluate("//style:style[@style:name='" + sname + "']", contentDom, XPathConstants.NODESET); if (nl.getLength() == 0) { OdfOfficeAutomaticStyles autost = (OdfOfficeAutomaticStyles) xpath.evaluate("//office:automatic-styles", contentDom, XPathConstants.NODE); OdfStyle frst = (OdfStyle) OdfXMLFactory.newOdfElement(contentDom, StyleStyleElement.ELEMENT_NAME); frst.setStyleNameAttribute(sname); frst.setStyleFamilyAttribute(family); frst.setStyleParentStyleNameAttribute("Standard"); autost.appendChild(frst); OdfStyleTextProperties stp = (OdfStyleTextProperties) OdfXMLFactory.newOdfElement(contentDom, StyleTextPropertiesElement.ELEMENT_NAME); if (font != null) { declareFont(font); stp.setStyleFontNameAttribute(font); } if (size != null) { stp.setFoFontSizeAttribute(size + "pt"); stp.setStyleFontSizeAsianAttribute(size + "pt"); stp.setStyleFontSizeComplexAttribute(size + "pt"); } if ((flags & SWT.BOLD) != 0) { stp.setFoFontWeightAttribute("bold"); } if ((flags & SWT.ITALIC) != 0) { stp.setFoFontStyleAttribute("italic"); } // If we have a paragraph style we might need to apply // alignment settings. if ((flags & ALIGN) != 0 && family.compareTo("paragraph") == 0) { OdfStyleParagraphProperties pp = (OdfStyleParagraphProperties) OdfXMLFactory.newOdfElement(contentDom, StyleParagraphPropertiesElement.ELEMENT_NAME); if ((flags & SWT.LEFT) != 0) { pp.setFoTextAlignAttribute("left"); } else if ((flags & SWT.RIGHT) != 0) { pp.setFoTextAlignAttribute("right"); } else if ((flags & SWT.CENTER) != 0) { pp.setFoTextAlignAttribute("center"); } frst.appendChild(pp); } frst.appendChild(stp); } } catch (Exception e) { e.printStackTrace(); } } /** @return Label for paragraph style */ public String getParagraphLbl(){ String lbl = label() + "_pg"; createStyle(lbl, "paragraph"); return lbl; } /** @return Label for text style */ public String getTextLbl(){ String lbl = label() + "_txt"; createStyle(lbl, "text"); return lbl; } } private Process editor_process; private File file; private OdfTextDocument odt; private Style curStyle; private Composite comp; private Label filename_label; private Button open_button; private Button import_button; private static final String pluginID = "com.hilotec.elexis.opendocument"; private static final String NoFileOpen = "Dateiname: Keine Datei geöffnet"; private static Logger logger = LoggerFactory.getLogger(pluginID); private static int cnt = 0; private String getTempPrefix(){ cnt += 1; StringBuffer sb = new StringBuffer(); Patient actPatient = ElexisEventDispatcher.getSelectedPatient(); if (actPatient != null) { sb.append(cnt + "_" + actPatient.getName() + "_"); sb.append(actPatient.getVorname() + "_"); } DateFormat dateFormat = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss_S"); Date date = new Date(); sb.append(dateFormat.format(date)); return sb.toString().replaceAll("[^0-9A-Za-z]", "_"); } private boolean editorRunning(){ if (editor_process == null) return false; try { int exitValue = editor_process.exitValue(); return false; } catch (IllegalThreadStateException e) { return true; } } private synchronized void odtSync(){ if (file == null || odt == null || editorRunning()) { return; } try { odt.save(file); logger.info("odtSync: completed " + file.length() + " saved"); } catch (Exception e) { // TODO e.printStackTrace(); } } /** * Sicherstellen dass kein Editor geoeffnet ist. Falls einer geoeffnet ist, wird eine * Fehlermeldung mit einem entsprechenden Hinweis angezeigt. * * @return True wenn keine Instanz mehr geoeffnet ist. */ private boolean ensureClosed(){ Patient actPatient = ElexisEventDispatcher.getSelectedPatient(); if (actPatient != null) { logger.info("ensureClosed: " + actPatient.getVorname() + " " + actPatient.getName().toString()); } while (editorRunning()) { logger.info("Editor already opened file " + file.getAbsolutePath()); SWTHelper .showError( "Editor bereits geöffnet", "Es scheint bereits ein Editor geöffnet zu sein für " + file.getAbsolutePath() + " geöffnet zu sein.\n\n" + "Falls Sie sicher sind, dass kein Editor diese Datei mehr offen hat, müssen Sie Elexis neu starten.\n\n" + "Falls Sie diese Warnung nicht beachten werden die in der Datei gemachten Änderungen nicht in der Elexis Datenbank gespeichert!"); return false; } return true; } private void openEditor(){ logger.info("openEditor "+file); if (file == null || !ensureClosed()) { return; } odtSync(); String editor = CoreHub.localCfg.get(Preferences.P_EDITOR, ""); String argstr = CoreHub.localCfg.get(Preferences.P_EDITARGS, ""); String baseName = "open_odf.sh"; if (System.getProperty("os.name").toLowerCase().indexOf("win") >= 0) baseName = "open_odf.bat"; String scriptFile = PlatformHelper.getBasePath(pluginID) + File.separator + "rsc" + File.separator + baseName; logger.info(scriptFile); if (editor.length() == 0) { SWTHelper.showError("Kein Editor gesetzt", "In den Einstellungen wurde kein Editor konfiguriert."); return; } File scriptShell = new File(scriptFile); if (!scriptShell.canExecute()) scriptShell.setExecutable(true); String args = (editor + "\n" + argstr + "\n" + file.getAbsolutePath()); if (CoreHub.localCfg.get(Preferences.P_WRAPPERSCRIPT, true)) args = scriptFile + "\n" + args; Patient actPatient = ElexisEventDispatcher.getSelectedPatient(); String personalia = (actPatient!=null) ? actPatient.getPersonalia() : "null"; logger.info("openEditor: " + personalia + " as " + file.getAbsolutePath()); ProcessBuilder pb = new ProcessBuilder(args.split("\n")); filename_label.setText(file.getAbsolutePath()); try { editor_process = pb.start(); odt = null; (new Thread() { public void run(){ try { editor_process.waitFor(); odt = (OdfTextDocument) OdfTextDocument.loadDocument(file); logger.info("openEditor: exitValue " + editor_process.exitValue() + " done " + file.getAbsolutePath() + " NOT closing odt"); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { editor_process = null; Display.getDefault().asyncExec(new Runnable() { public void run(){ filename_label.setText(NoFileOpen); logger.info("openEditor: updated filename_label"); } }); } } }).start(); } catch (IOException e) { e.printStackTrace(); } } private void importFile(){ if (file == null || !ensureClosed()) { return; } odtSync(); FileDialog fd = new FileDialog(UiDesk.getTopShell(), SWT.OPEN); fd.setFilterExtensions(new String[] { "*.odt" }); String path = fd.open(); if (path != null) { try { logger.info("importFile: " + path); OdfTextDocument ndoc = (OdfTextDocument) OdfTextDocument.loadDocument(path); if (ndoc != null) { odt = ndoc; fileValid(); } odtSync(); } catch (Exception e) { SWTHelper.showError("Fehler beim Import", e.getMessage()); } } } public File exportPDF() { if (file == null || !ensureClosed()) { return null; } odtSync(); File pdffile = new File( file.getAbsoluteFile().getPath().replaceAll("\\.odt$", ".pdf")); String pdfconv = CoreHub.localCfg.get(Preferences.P_PDFCONVERTER, ""); String pdfargs = CoreHub.localCfg.get(Preferences.P_PDFARGS, ""); if (pdfconv.length() == 0) { SWTHelper.showError("Kein Konvertierungsbefehl gesetzt", "In den Einstellungen wurde kein Befehl zum Konvertieren " + "nach PDF konfiguriert."); return null; } String args[] = (pdfconv + "\n" + pdfargs + "\n" + file.getAbsolutePath()).split("[\n\r]+"); ProcessBuilder pb = new ProcessBuilder(args); try { pb.directory(file.getAbsoluteFile().getParentFile()); final Process convert = pb.start(); convert.waitFor(); return pdffile; } catch (Exception e) { e.printStackTrace(); } return null; } public boolean print(String toPrinter, String toTray, boolean wait){ logger.info("String: " + (file != null)); if (file == null || !ensureClosed()) { return false; } odtSync(); String editor = CoreHub.localCfg.get(Preferences.P_EDITOR, "oowriter"); String argstr = CoreHub.localCfg.get(Preferences.P_PRINTARGS, ""); String args[] = (editor + "\n" + argstr + "\n" + file.getAbsolutePath()).split("\n"); ProcessBuilder pb = new ProcessBuilder(args); try { logger.info("print: " + args); editor_process = pb.start(); editor_process.waitFor(); logger.info("print waitFor done: " + args); filename_label.setText(NoFileOpen); } catch (Exception e) { e.printStackTrace(); } finally { editor_process = null; } return true; } @Override public Composite createContainer(Composite parent, ICallback handler){ if (comp == null) { comp = new Composite(parent, SWT.NONE); RowLayout layout = new RowLayout(SWT.VERTICAL); layout.wrap = true; layout.fill = false; layout.justify = false; comp.setLayout(layout); RowData data = new RowData(); filename_label = new Label(comp, SWT.PUSH); filename_label.setText(NoFileOpen); filename_label.setLayoutData(data); data.width = 400; open_button = new Button(comp, SWT.PUSH); open_button.setText("Editor öffnen"); open_button.addListener(SWT.Selection, new Listener() { public void handleEvent(Event event){ openEditor(); } }); data = new RowData(); open_button.setLayoutData(data); import_button = new Button(comp, SWT.PUSH); import_button.setText("Datei importieren"); import_button.addListener(SWT.Selection, new Listener() { public void handleEvent(Event event){ importFile(); } }); import_button.setLayoutData(data); comp.pack(); Composite exporters = new Composite(parent, SWT.NONE); exporters.setLayout(new GridLayout()); Exporter[] exps = Export.getExporters(); for (Exporter e: exps) { Button b = new Button(exporters, SWT.PUSH); b.setText(e.getLabel()); b.setData(e); b.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { Button b = (Button) e.widget; Exporter ex = (Exporter) b.getData(); File f = exportPDF(); if (f != null) ex.export(f.getPath()); } }); } exporters.update(); } return comp; } private void fileValid(){ open_button.setEnabled(true); curStyle = new Style(); } @Override public void dispose(){ logger.info("dispose: "); } private void closeFile(){ // System.out.println("closeFile()"); logger.info("closeFile: " + file.toString()); odtSync(); file.delete(); file = null; } @Override public boolean clear(){ logger.info("clear: "); SWTHelper.showError("TODO", "TODO: clear()"); return false; } @Override public boolean createEmptyDocument(){ logger.info("createEmptyDocument: "); if (!ensureClosed()) { return false; } if (file != null) { closeFile(); } try { file = File.createTempFile(getTempPrefix(), ".odt"); file.deleteOnExit(); logger.info("createEmptyDocument: " + file.toString()); odt = OdfTextDocument.newTextDocument(); odt.save(file); fileValid(); logger.info("createEmptyDocument: save done: " + file.toString()); } catch (Exception e) { file = null; return false; } return true; } @Override public byte[] storeToByteArray(){ logger.info("storeToByteArray: editorRunning() " + editorRunning() + " file: " + file + " odt null " + (odt == null)); if (file == null || odt == null || editorRunning()) { return null; } ByteArrayOutputStream stream = new ByteArrayOutputStream(); if (stream == null) return null; try { odt.save(stream); logger.info("storeToByteArray: completed " + file.length() + " bytes"); } catch (Exception e) { e.printStackTrace(); return null; } return stream.toByteArray(); } @Override public boolean loadFromByteArray(byte[] bs, boolean asTemplate){ logger.info("loadFromByteArray: asTemplate " + asTemplate); ByteArrayInputStream stream = new ByteArrayInputStream(bs); return loadFromStream(stream, asTemplate); } @Override public boolean loadFromStream(InputStream is, boolean asTemplate){ logger.info("loadFromStream: " + (file != null)); if (!ensureClosed()) { return false; } if (file != null) { closeFile(); } try { file = File.createTempFile(getTempPrefix(), ".odt"); logger.info("loadFromStream: " + file.toString()); file.deleteOnExit(); odt = (OdfTextDocument) OdfDocument.loadDocument(is); odt.save(file); fileValid(); logger.info("loadFromStream: saved (but not yet converted) " + file.toString()); } catch (Exception e) { e.printStackTrace(); logger.info("loadFromStream: loading document failed "); return false; } return true; } /** * Ersetzt Tabs in einem Text-Node * * @see formatText * * @return Letzter enstandener Knoten */ private Text replaceTabs(OdfFileDom dom, Text text){ Node parent = text.getParentNode(); Text cur = text; int i; while ((i = cur.getTextContent().indexOf('\t')) >= 0) { Text next = cur.splitText(i); next.setTextContent(next.getTextContent().substring(1)); OdfTextTab tab = (OdfTextTab) OdfXMLFactory.newOdfElement(dom, OdfTextTab.ELEMENT_NAME); parent.insertBefore(tab, next); cur = next; } return cur; } /** * Text-Node formatieren. Dabei werden Newlines und Tabs ersetzt. Der Knoten kann unter * Umstaenden aufgespalten werden. {text} entspricht in dem Fall dem ersten Stueck. * * @return Letzter Knoten, der beim aufsplitten entstanden ist * @throws Exception */ private Text formatText(OdfFileDom dom, Text text) throws Exception{ Node parent = text.getParentNode(); Text cur = text; int i; // XXX: Hack fuer Text unterstrichen darzustellen String textContent = text.getTextContent(); if (textContent.startsWith("_") && textContent.endsWith("_")) { // _ Pre/Suffix entfernen text.setTextContent(textContent.substring(1, textContent.length() - 1)); if (parent instanceof OdfStylableElement) { OdfStylableElement stel = (OdfStylableElement) parent; OdfFileDom contentDom = odt.getContentDom(); // Neuen Stil erstellen mit unterstrichen aktiviert OdfStyle newst = createNewStyle("ul_", contentDom, odt.getStylesDom()); newst.setStyleFamilyAttribute("paragraph"); OdfStyleTextProperties stp = (OdfStyleTextProperties) OdfXMLFactory.newOdfElement(contentDom, StyleTextPropertiesElement.ELEMENT_NAME); stp.setStyleTextUnderlineStyleAttribute("solid"); stp.setStyleTextUnderlineWidthAttribute("auto"); stp.setStyleTextUnderlineColorAttribute("font-color"); newst.appendChild(stp); // Originalstil als parent-Stil // FIXME: Funktioniert so nicht wie gewuenscht, die // style:text-properties muessten aus dem Elternstil kopiert // werden. String oldst = stel.getStyleName(); if (oldst != null && !oldst.isEmpty()) newst.setStyleParentStyleNameAttribute(oldst); stel.setStyleName(newst.getStyleNameAttribute()); } } while ((i = cur.getTextContent().indexOf('\n')) >= 0) { Text next = cur.splitText(i); next.setTextContent(next.getTextContent().substring(1)); OdfTextLineBreak lbrk = (OdfTextLineBreak) OdfXMLFactory.newOdfElement(dom, OdfTextLineBreak.ELEMENT_NAME); parent.insertBefore(lbrk, next); replaceTabs(dom, cur); cur = next; } return replaceTabs(dom, cur); } private boolean searchNode(Node n, Pattern pat, List<Text> matches, boolean onlyFirst){ boolean result = false; Node child = n.getFirstChild(); while (child != null) { if (child instanceof Text) { Text bit = (Text) child; String content = bit.getTextContent(); Matcher m = pat.matcher(content); if (m.find()) { int start = m.start(); int end = m.end(); // Wenn noetig fuehrendes Stueck abschneiden if (start != 0) { bit = bit.splitText(start); end -= start; } // Wenn noetig nachfolgendes Stueck abschneiden if (end != bit.getTextContent().length()) { bit.splitText(end); } result = true; matches.add(bit); child = bit; if (onlyFirst) { return true; } } } child = child.getNextSibling(); } return result; } /** * Text-Nodes finden deren Inhalt das uebergebene Pattern matcht. Dabei werden die Folgenden * Elementtypen durchsucht: - text:p - text:span Es koennen vorhandene Text-Knoten aufgespalten * werden. */ private List<Text> findTextNode(OdfFileDom dom, XPath xpath, Pattern pat, boolean onlyFirst) throws Exception{ List<Text> result = new ArrayList<Text>(); String types[] = { "//text:p", "//text:span" }; for (String t : types) { NodeList bits = (NodeList) xpath.evaluate(t, dom, XPathConstants.NODESET); for (int i = 0; i < bits.getLength(); i++) { Node n = bits.item(i); if (searchNode(n, pat, result, onlyFirst) && onlyFirst) { return result; } } } return result; } /** * Tabelle erstellen, an der stelle an der match steht. match wird aus dem Dokument entfernt. * * Bei den Breiten werden Prozentwerte erwartet, oder null, falls die Breite auf alle Spalten * gleichmaessig verteilt werden soll. */ private void makeTableAt(OdfFileDom dom, Text match, String[][] content, int[] widths) throws Exception{ Node tableParent = match.getParentNode(); // Find Parent-Node for table while (tableParent instanceof TextSpanElement) { tableParent = tableParent.getParentNode(); } Node before = tableParent; tableParent = tableParent.getParentNode(); // Create table TableTableElement table = (TableTableElement) OdfXMLFactory.newOdfElement(dom, TableTableElement.ELEMENT_NAME); tableParent.insertBefore(table, before); // Remove reference node // FIXME: There is probably a better solution before.getParentNode().removeChild(before); // Initialize columns if (content.length == 0) { return; } int colcount = 0; for (String[] row : content) { colcount = Math.max(colcount, row.length); } if (widths == null) { // Create a column declaration for all columns TableTableColumnElement ttc = (TableTableColumnElement) OdfXMLFactory.newOdfElement(dom, TableTableColumnElement.ELEMENT_NAME); ttc.setTableNumberColumnsRepeatedAttribute(colcount); table.appendChild(ttc); } else { float percentval = 65535f / 100f; for (int i = 0; i < widths.length; i++) { // Create Style for this column OdfStyle cst = createNewStyle("col", odt.getContentDom(), odt.getStylesDom()); String stname = cst.getStyleNameAttribute(); cst.setStyleFamilyAttribute("table-column"); OdfStyleTableColumnProperties stcp = (OdfStyleTableColumnProperties) OdfXMLFactory.newOdfElement(dom, StyleTableColumnPropertiesElement.ELEMENT_NAME); stcp.setStyleRelColumnWidthAttribute(Integer .toString((int) (widths[i] * percentval))); cst.appendChild(stcp); // Create Column declaration for this column TableTableColumnElement ttc = (TableTableColumnElement) OdfXMLFactory.newOdfElement(dom, TableTableColumnElement.ELEMENT_NAME); ttc.setStyleName(stname); table.appendChild(ttc); } } for (String[] row : content) { // Create row TableTableRowElement ttre = table.newTableTableRowElement(); table.appendChild(ttre); for (int i = 0; i < row.length; i++) { String col = row[i]; boolean last = (i == row.length - 1); if (col == null) { col = ""; } // Create cell TableTableCellElement ttce = ttre.newTableTableCellElement(); ttce.setOfficeValueTypeAttribute("string"); ttre.appendChild(ttce); // If this is the last column, and we don't have values for all // columns, we need to set colspan. if (last && row.length < colcount) { ttce.setTableNumberColumnsSpannedAttribute(colcount - i); } TextPElement tp = (TextPElement) OdfXMLFactory.newOdfElement(dom, TextPElement.ELEMENT_NAME); tp.setStyleName(curStyle + "_pg"); tp.setTextContent(col); ttce.appendChild(tp); // Format cell content Text t = (Text) tp.getFirstChild(); if (t != null) { formatText(dom, t); } } } } /** * Tabelle in der sich der angegebene Platzhalter befindet befuellen. Dafuer wird die * Vorlagezeile 1:1 kopiert (insbesondere werden styles und breiten uebernommen) und die spalten * vom Plathalter an werden alle mit dem Inhalt aus content ueberschrieben. Spalten vor dem * angegebenen Platzhalter und ueberschuessige danach werden 1:1 kopiert. */ private void fillTableAt(OdfFileDom dom, Text match, String[][] content) throws Exception{ Node cellNode = match.getParentNode(); TableTableRowElement row; TableTableCellElement cell; int cellIndex = 0; // Find row-node while (!(cellNode instanceof TableTableCellElement)) { cellNode = cellNode.getParentNode(); } cell = (TableTableCellElement) cellNode; row = (TableTableRowElement) cell.getParentNode(); Node parent = row.getParentNode(); // Zellenindex ausfindig machen NodeList cellList = row.getChildNodes(); for (int i = 0; i < cellList.getLength(); i++) { Node n = cellList.item(i); if (!(n instanceof TableTableCellElement)) continue; if (n == cell) break; cellIndex++; } for (String[] rData : content) { if (rData == null) continue; TableTableRowElement r = (TableTableRowElement) row.cloneNode(true); // Durch spalten und anderen Inhalt iterieren und entsprechende // Zellen befuellen. int i = 0; NodeList nl = r.getChildNodes(); for (int j = 0; j < nl.getLength(); j++) { Node n = nl.item(j); if (!(n instanceof TableTableCellElement)) continue; TableTableCellElement c = (TableTableCellElement) n; if (i >= cellIndex && (i - cellIndex + 1 <= rData.length)) { // FIXME TextPElement pe = (TextPElement) c.getChildNodes().item(0); pe.setTextContent(StringTool.unNull(rData[i - cellIndex])); Text t = (Text) pe.getFirstChild(); if (t != null) { formatText(dom, t); } } i++; } parent.insertBefore(r, row); } parent.removeChild(row); } private void replaceTableFills(OdfFileDom dom, XPath xpath, Pattern pat, boolean onlyFirst, ReplaceCallback cb) throws Exception{ String spat = pat.pattern(); spat = spat.replaceAll("\\\\\\[", "\\\\{"); spat = spat.replaceAll("\\\\\\]", "\\\\}"); Pattern npat = Pattern.compile(spat); List<Text> matches = findTextNode(dom, xpath, npat, onlyFirst); for (Text match : matches) { String text = match.getTextContent().replaceAll("\\{", "[").replaceAll("\\}", "]"); Object replacement = cb.replace(text); if (replacement instanceof String[][]) { try { fillTableAt(dom, match, (String[][]) replacement); } catch (Exception e) { e.printStackTrace(); } } } } private int findOrReplaceIn(OdfFileDom dom, Pattern pat, ReplaceCallback cb, XPath xpath) throws Exception{ replaceTableFills(dom, xpath, pat, false, cb); List<Text> matches = findTextNode(dom, xpath, pat, false); for (Text match : matches) { String text = match.getTextContent(); Object replacement = cb.replace(text); String replstr; if (replacement == null) {} else if (replacement instanceof String) { replstr = (String) replacement; if (replstr.compareTo(text) != 0) { match.setTextContent(replstr); formatText(dom, match); } } else if (replacement instanceof String[][]) { try { makeTableAt(dom, match, (String[][]) replacement, null); } catch (Exception e) { e.printStackTrace(); } } else { replstr = "???Unbekannter Typ???"; if (replstr.compareTo(text) != 0) { match.setTextContent(replstr); } } } return matches.size(); } @Override public boolean findOrReplace(String pattern, ReplaceCallback cb){ if (editorRunning() || file == null) { return false; } int count = 0; try { Pattern pat = Pattern.compile(pattern); OdfFileDom contentDom = odt.getContentDom(); OdfFileDom styleDom = odt.getStylesDom(); XPath xpath = odt.getXPath(); count += findOrReplaceIn(contentDom, pat, cb, xpath); count += findOrReplaceIn(styleDom, pat, cb, xpath); odtSync(); } catch (Exception e) { e.printStackTrace(); } return count > 0; } @Override public boolean insertTable(String place, int properties, String[][] contents, int[] columnSizes){ // System.out.println("insertTable()" + this.hashCode()); if (!ensureClosed() || file == null) { return false; } try { OdfFileDom contentDom = odt.getContentDom(); XPath xpath = odt.getXPath(); List<Text> texts = findTextNode(contentDom, xpath, Pattern.compile(Pattern.quote(place)), true); if (texts.size() == 0) { return false; } Text txt = texts.get(0); makeTableAt(contentDom, txt, contents, columnSizes); // TODO: Style odtSync(); return true; } catch (Exception e) { e.printStackTrace(); return false; } } @Override public Object insertText(String marke, String text, int adjust){ if (!ensureClosed() || file == null) { return null; } // System.out.println("insertText('" + marke + "', '" + text + "')"); try { OdfFileDom contentDom = odt.getContentDom(); XPath xpath = odt.getXPath(); List<Text> texts = findTextNode(contentDom, xpath, Pattern.compile(Pattern.quote(marke)), true); if (texts.size() == 0) { return null; } Text txt = texts.get(0); txt.setTextContent(text); txt = formatText(contentDom, txt); // TODO: Style odtSync(); return txt; } catch (Exception e) { e.printStackTrace(); return null; } } @Override public Object insertText(Object pos, String text, int adjust){ if (!ensureClosed() || file == null || pos == null) { return null; } // System.out.println("insertText2('" + text + "')"); try { OdfFileDom contentDom = odt.getContentDom(); Text prev = (Text) pos; curStyle.setAlign(adjust); TextSpanElement span = (TextSpanElement) OdfXMLFactory.newOdfElement(contentDom, TextSpanElement.ELEMENT_NAME); span.setTextContent(text); span.setStyleName(curStyle.getTextLbl()); int i; Text txt = prev; for (i = 0; i < span.getChildNodes().getLength(); i++) { Node n = span.getChildNodes().item(i); if (n instanceof Text) { txt = (Text) n; formatText(contentDom, txt); } } prev.getParentNode().insertBefore(span, prev.getNextSibling()); curStyle.clearAlign(); return txt; } catch (Exception e) { e.printStackTrace(); return null; } } /** * Finde einen nicht benutzten Style-Namen der mit prefix beginnt, und mit einer beliebigen Zahl * endet. */ private String generateStyleName(String prefix, OdfFileDom contentDom, OdfFileDom styleDom, XPath xpath){ NodeList nl; // TODO: Muesste sich doch in konstanter Zeit machen lassen. ;-) for (int i = 0;; i++) { String cur = prefix + i; String xp = "//*[@style:name='" + cur + "']"; try { nl = (NodeList) xpath.evaluate(xp, contentDom, XPathConstants.NODESET); if (nl.getLength() > 0) { continue; } nl = (NodeList) xpath.evaluate(xp, styleDom, XPathConstants.NODESET); if (nl.getLength() > 0) { continue; } return cur; } catch (XPathExpressionException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } private OdfStyle createNewStyle(String prefix, OdfFileDom contentDom, OdfFileDom styleDom) throws Exception{ XPath xpath = odt.getXPath(); String name = generateStyleName(prefix, contentDom, styleDom, xpath); OdfOfficeAutomaticStyles autost = (OdfOfficeAutomaticStyles) xpath.evaluate("//office:automatic-styles", contentDom, XPathConstants.NODE); OdfStyle style = (OdfStyle) OdfXMLFactory.newOdfElement(contentDom, StyleStyleElement.ELEMENT_NAME); style.setStyleNameAttribute(name); autost.appendChild(style); return style; } @Override public Object insertTextAt(int x, int y, int w, int h, String text, int adjust){ if (!ensureClosed() || file == null) { return null; } try { OdfFileDom contentDom = odt.getContentDom(); OdfFileDom styleDom = odt.getStylesDom(); XPath xpath = odt.getXPath(); curStyle.setAlign(adjust); // Generate Styles OdfStyle frst = createNewStyle("fr", contentDom, styleDom); String frstyle = frst.getStyleNameAttribute(); frst.setStyleFamilyAttribute("graphic"); frst.setStyleParentStyleNameAttribute("Frame"); OdfStyleGraphicProperties gsp = (OdfStyleGraphicProperties) OdfXMLFactory.newOdfElement(contentDom, StyleGraphicPropertiesElement.ELEMENT_NAME); gsp.setStyleRunThroughAttribute("foreground"); gsp.setStyleWrapAttribute("dynamic"); gsp.setStyleNumberWrappedParagraphsAttribute("no-limit"); gsp.setStyleVerticalPosAttribute("from-top"); gsp.setStyleVerticalRelAttribute("page-content"); gsp.setStyleHorizontalPosAttribute("from-left"); gsp.setStyleHorizontalRelAttribute("page-content"); gsp.setStyleBackgroundTransparencyAttribute("100%"); gsp.setStyleShadowAttribute("none"); // Strange, if transparent is chosen OO3 doesent display it // transparently gsp.setFoBackgroundColorAttribute("#ffffff"); gsp.setFoPaddingAttribute("0cm"); gsp.setFoBorderAttribute("none"); frst.appendChild(gsp); OdfStyleBackgroundImage bgimg = (OdfStyleBackgroundImage) OdfXMLFactory.newOdfElement(contentDom, StyleBackgroundImageElement.ELEMENT_NAME); gsp.appendChild(bgimg); OdfStyleColumns scols = (OdfStyleColumns) OdfXMLFactory.newOdfElement(contentDom, StyleColumnsElement.ELEMENT_NAME); scols.setFoColumnCountAttribute(1); scols.setFoColumnGapAttribute("0cm"); gsp.appendChild(scols); // Generate Content OdfOfficeText officeText = (OdfOfficeText) xpath.evaluate("//office:text", contentDom, XPathConstants.NODE); OdfDrawFrame frame = (OdfDrawFrame) OdfXMLFactory.newOdfElement(contentDom, DrawFrameElement.ELEMENT_NAME); frame.setSvgXAttribute(x + "mm"); frame.setSvgYAttribute(y + "mm"); frame.setSvgWidthAttribute(w + "mm"); // FIXME: Unschoener Workaround fuer platzproblem bei // Einzahlungsschein frame.setSvgHeightAttribute((h + 1) + "mm"); frame.setTextAnchorTypeAttribute("page"); frame.setTextAnchorPageNumberAttribute(1); frame.setDrawZIndexAttribute(0); frame.setDrawStyleNameAttribute(frstyle); frame.setDrawNameAttribute("Frame" + frstyle); officeText.insertBefore(frame, officeText.getFirstChild()); OdfDrawTextBox textbox = (OdfDrawTextBox) OdfXMLFactory.newOdfElement(contentDom, DrawTextBoxElement.ELEMENT_NAME); frame.appendChild(textbox); OdfTextParagraph para = (OdfTextParagraph) OdfXMLFactory.newOdfElement(contentDom, TextPElement.ELEMENT_NAME); para.setTextContent(text); para.setStyleName(curStyle.getParagraphLbl()); textbox.appendChild(para); // TODO: Sauber? Text txt = (Text) para.getChildNodes().item(0); formatText(contentDom, txt); curStyle.clearAlign(); odtSync(); return txt; } catch (Exception e) { e.printStackTrace(); } // TODO Auto-generated method stub return null; } @Override public PageFormat getFormat(){ // System.out.println("getFormat()"); // TODO Auto-generated method stub return null; } @Override public String getMimeType(){ return "application/vnd.oasis.opendocument.text"; } @Override public void setFocus(){ // TODO Auto-generated method stub } @Override public boolean setFont(String name, int style, float size){ if (!ensureClosed() || file == null) { return false; } curStyle.setFont(name, style, size); return true; } @Override public void setFormat(PageFormat f){ // System.out.println("setFormat"); // TODO Auto-generated method stub } @Override public void setSaveOnFocusLost(boolean bSave){ // System.out.println("setSaveOnFocusLost"); // TODO Auto-generated method stub } @Override public boolean setStyle(int style){ curStyle.setStyle(style); return true; } @Override public void showMenu(boolean b){ // TODO Auto-generated method stub } @Override public void showToolbar(boolean b){ // TODO Auto-generated method stub } @Override public void setInitializationData(IConfigurationElement config, String propertyName, Object data) throws CoreException{ // TODO Auto-generated method stub } @Override public boolean isDirectOutput(){ return false; } @Override public void setParameter(Parameter parameter){ // TODO Auto-generated method stub } @Override public void initTemplatePrintSettings(String template){ // TODO Auto-generated method stub } }