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