001/*
002 * (C) Copyright 2006-20014 Nuxeo SA (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl-2.1.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     Nuxeo - initial API and implementation
016 *
017 */
018
019package org.nuxeo.template.processors.docx;
020
021import java.io.File;
022import java.io.IOException;
023import java.io.InputStream;
024import java.io.Serializable;
025import java.text.ParseException;
026import java.text.SimpleDateFormat;
027import java.util.ArrayList;
028import java.util.Date;
029import java.util.List;
030import java.util.zip.ZipEntry;
031import java.util.zip.ZipInputStream;
032
033import org.dom4j.Document;
034import org.dom4j.DocumentException;
035import org.dom4j.DocumentHelper;
036import org.dom4j.tree.DefaultElement;
037import org.nuxeo.common.utils.FileUtils;
038import org.nuxeo.common.utils.ZipUtils;
039import org.nuxeo.ecm.core.api.Blob;
040import org.nuxeo.ecm.core.api.Blobs;
041import org.nuxeo.ecm.core.api.CloseableFile;
042import org.nuxeo.ecm.core.api.DocumentModel;
043import org.nuxeo.ecm.core.api.PropertyException;
044import org.nuxeo.ecm.core.api.model.Property;
045import org.nuxeo.runtime.api.Framework;
046import org.nuxeo.template.api.InputType;
047import org.nuxeo.template.api.TemplateInput;
048import org.nuxeo.template.api.adapters.TemplateBasedDocument;
049import org.nuxeo.template.processors.AbstractTemplateProcessor;
050import org.nuxeo.template.processors.BidirectionalTemplateProcessor;
051
052/**
053 * WordXML implementation of the {@link BidirectionalTemplateProcessor}. Uses Raw XML parsing : legacy code for now.
054 *
055 * @author Tiry (tdelprat@nuxeo.com)
056 */
057public class WordXMLRawTemplateProcessor extends AbstractTemplateProcessor implements BidirectionalTemplateProcessor {
058
059    public static SimpleDateFormat wordXMLDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss'Z'");
060
061    public static final String TEMPLATE_TYPE = "wordXMLTemplate";
062
063    @Override
064    @SuppressWarnings("rawtypes")
065    public Blob renderTemplate(TemplateBasedDocument templateDocument, String templateName) throws IOException {
066
067        File workingDir = getWorkingDir();
068
069        Blob blob = templateDocument.getTemplateBlob(templateName);
070        String fileName = blob.getFilename();
071        List<TemplateInput> params = templateDocument.getParams(templateName);
072
073        try (CloseableFile source = blob.getCloseableFile()) {
074            ZipUtils.unzip(source.getFile(), workingDir);
075        }
076
077        File xmlCustomFile = new File(workingDir.getAbsolutePath() + "/docProps/custom.xml");
078
079        String xmlContent = FileUtils.readFile(xmlCustomFile);
080
081        Document xmlDoc;
082        try {
083            xmlDoc = DocumentHelper.parseText(xmlContent);
084        } catch (DocumentException e) {
085            throw new IOException(e);
086        }
087
088        List nodes = xmlDoc.getRootElement().elements();
089
090        for (Object node : nodes) {
091            DefaultElement elem = (DefaultElement) node;
092            if ("property".equals(elem.getName())) {
093                String name = elem.attributeValue("name");
094                TemplateInput param = getParamByName(name, params);
095                DefaultElement valueElem = (DefaultElement) elem.elements().get(0);
096                String strValue = "";
097                if (param.isSourceValue()) {
098                    Property property = templateDocument.getAdaptedDoc().getProperty(param.getSource());
099                    if (property != null) {
100                        Serializable value = templateDocument.getAdaptedDoc().getPropertyValue(param.getSource());
101                        if (value != null) {
102                            if (value instanceof Date) {
103                                strValue = wordXMLDateFormat.format((Date) value);
104                            } else {
105                                strValue = value.toString();
106                            }
107                        }
108                    }
109                } else {
110                    if (InputType.StringValue.equals(param.getType())) {
111                        strValue = param.getStringValue();
112                    } else if (InputType.BooleanValue.equals(param.getType())) {
113                        strValue = param.getBooleanValue().toString();
114                    } else if (InputType.DateValue.equals(param.getType())) {
115                        strValue = wordXMLDateFormat.format(param.getDateValue());
116                    }
117                }
118                valueElem.setText(strValue);
119            }
120        }
121
122        String newXMLContent = xmlDoc.asXML();
123
124        File newZipFile = File.createTempFile("newWordXMLTemplate", ".docx");
125        xmlCustomFile.delete();
126        File newXMLFile = new File(xmlCustomFile.getAbsolutePath());
127        FileUtils.writeFile(newXMLFile, newXMLContent);
128
129        File[] files = workingDir.listFiles();
130        ZipUtils.zip(files, newZipFile);
131
132        // clean up
133        FileUtils.deleteTree(workingDir);
134
135        Blob newBlob = Blobs.createBlob(newZipFile);
136        Framework.trackFile(newZipFile, newBlob);
137        newBlob.setFilename(fileName);
138
139        return newBlob;
140    }
141
142    @Override
143    @SuppressWarnings("rawtypes")
144    public List<TemplateInput> getInitialParametersDefinition(Blob blob) throws IOException {
145        List<TemplateInput> params = new ArrayList<>();
146
147        String xmlContent = readPropertyFile(blob.getStream());
148
149        Document xmlDoc;
150        try {
151            xmlDoc = DocumentHelper.parseText(xmlContent);
152        } catch (DocumentException e) {
153            throw new IOException(e);
154        }
155
156        List nodes = xmlDoc.getRootElement().elements();
157
158        for (Object node : nodes) {
159            DefaultElement elem = (DefaultElement) node;
160            if ("property".equals(elem.getName())) {
161                String name = elem.attributeValue("name");
162                DefaultElement valueElem = (DefaultElement) elem.elements().get(0);
163                String wordType = valueElem.getName();
164                InputType nxType = InputType.StringValue;
165                if (wordType.contains("lpwstr")) {
166                    nxType = InputType.StringValue;
167                } else if (wordType.contains("filetime")) {
168                    nxType = InputType.DateValue;
169                } else if (wordType.contains("bool")) {
170                    nxType = InputType.BooleanValue;
171                }
172
173                TemplateInput input = new TemplateInput(name);
174                input.setType(nxType);
175                params.add(input);
176            }
177        }
178        return params;
179    }
180
181    protected TemplateInput getParamByName(String name, List<TemplateInput> params) {
182        for (TemplateInput param : params) {
183            if (param.getName().equals(name)) {
184                return param;
185            }
186        }
187        return null;
188    }
189
190    public String readPropertyFile(InputStream in) throws IOException {
191        ZipInputStream zIn = new ZipInputStream(in);
192        ZipEntry zipEntry = zIn.getNextEntry();
193        String xmlContent = null;
194        while (zipEntry != null) {
195            if (zipEntry.getName().equals("docProps/custom.xml")) {
196                StringBuilder sb = new StringBuilder();
197                byte[] buffer = new byte[BUFFER_SIZE];
198                int read;
199                while ((read = zIn.read(buffer)) != -1) {
200                    sb.append(new String(buffer, 0, read));
201                }
202                xmlContent = sb.toString();
203                break;
204            }
205            zipEntry = zIn.getNextEntry();
206        }
207        zIn.close();
208        return xmlContent;
209    }
210
211    @Override
212    @SuppressWarnings("rawtypes")
213    public DocumentModel updateDocumentFromBlob(TemplateBasedDocument templateDocument, String templateName) throws IOException {
214
215        Blob blob = templateDocument.getTemplateBlob(templateName);
216
217        String xmlContent = readPropertyFile(blob.getStream());
218
219        if (xmlContent == null) {
220            return templateDocument.getAdaptedDoc();
221        }
222
223        Document xmlDoc;
224        try {
225            xmlDoc = DocumentHelper.parseText(xmlContent);
226        } catch (DocumentException e) {
227            throw new IOException(e);
228        }
229
230        List nodes = xmlDoc.getRootElement().elements();
231
232        DocumentModel adaptedDoc = templateDocument.getAdaptedDoc();
233        List<TemplateInput> params = templateDocument.getParams(templateName);
234
235        for (Object node : nodes) {
236            DefaultElement elem = (DefaultElement) node;
237            if ("property".equals(elem.getName())) {
238                String name = elem.attributeValue("name");
239                TemplateInput param = getParamByName(name, params);
240                DefaultElement valueElem = (DefaultElement) elem.elements().get(0);
241                String xmlValue = valueElem.getTextTrim();
242                if (param.isSourceValue()) {
243                    // XXX this needs to be rewritten
244
245                    if (String.class.getSimpleName().equals(param.getType())) {
246                        adaptedDoc.setPropertyValue(param.getSource(), xmlValue);
247                    } else if (InputType.BooleanValue.equals(param.getType())) {
248                        adaptedDoc.setPropertyValue(param.getSource(), new Boolean(xmlValue));
249                    } else if (Date.class.getSimpleName().equals(param.getType())) {
250                        try {
251                            adaptedDoc.setPropertyValue(param.getSource(), wordXMLDateFormat.parse(xmlValue));
252                        } catch (PropertyException | ParseException e) {
253                            throw new IOException(e);
254                        }
255                    }
256                } else {
257                    if (InputType.StringValue.equals(param.getType())) {
258                        param.setStringValue(xmlValue);
259                    } else if (InputType.BooleanValue.equals(param.getType())) {
260                        param.setBooleanValue(new Boolean(xmlValue));
261                    } else if (InputType.DateValue.equals(param.getType())) {
262                        try {
263                            param.setDateValue(wordXMLDateFormat.parse(xmlValue));
264                        } catch (ParseException e) {
265                            throw new IOException(e);
266                        }
267                    }
268                }
269            }
270        }
271        adaptedDoc = templateDocument.saveParams(templateName, params, false);
272        return adaptedDoc;
273    }
274
275}