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