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.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.apache.commons.io.FileUtils;
034import org.dom4j.Document;
035import org.dom4j.DocumentException;
036import org.dom4j.DocumentHelper;
037import org.dom4j.tree.DefaultElement;
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 final String WORD_XML_DATE_FORMAT = "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.readFileToString(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                                SimpleDateFormat wordXMLDateFormat = new SimpleDateFormat(WORD_XML_DATE_FORMAT);
104                                strValue = wordXMLDateFormat.format((Date) value);
105                            } else {
106                                strValue = value.toString();
107                            }
108                        }
109                    }
110                } else {
111                    if (InputType.StringValue.equals(param.getType())) {
112                        strValue = param.getStringValue();
113                    } else if (InputType.BooleanValue.equals(param.getType())) {
114                        strValue = param.getBooleanValue().toString();
115                    } else if (InputType.DateValue.equals(param.getType())) {
116                        SimpleDateFormat wordXMLDateFormat = new SimpleDateFormat(WORD_XML_DATE_FORMAT);
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.writeStringToFile(newXMLFile, newXMLContent);
130
131        File[] files = workingDir.listFiles();
132        ZipUtils.zip(files, newZipFile);
133
134        // clean up
135        org.apache.commons.io.FileUtils.deleteDirectory(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)
216            throws IOException {
217
218        Blob blob = templateDocument.getTemplateBlob(templateName);
219
220        String xmlContent = readPropertyFile(blob.getStream());
221
222        if (xmlContent == null) {
223            return templateDocument.getAdaptedDoc();
224        }
225
226        Document xmlDoc;
227        try {
228            xmlDoc = DocumentHelper.parseText(xmlContent);
229        } catch (DocumentException e) {
230            throw new IOException(e);
231        }
232
233        List nodes = xmlDoc.getRootElement().elements();
234
235        DocumentModel adaptedDoc = templateDocument.getAdaptedDoc();
236        List<TemplateInput> params = templateDocument.getParams(templateName);
237
238        for (Object node : nodes) {
239            DefaultElement elem = (DefaultElement) node;
240            if ("property".equals(elem.getName())) {
241                String name = elem.attributeValue("name");
242                TemplateInput param = getParamByName(name, params);
243                DefaultElement valueElem = (DefaultElement) elem.elements().get(0);
244                String xmlValue = valueElem.getTextTrim();
245                if (param.isSourceValue()) {
246                    // XXX this needs to be rewritten
247
248                    if (String.class.getSimpleName().equals(param.getType())) {
249                        adaptedDoc.setPropertyValue(param.getSource(), xmlValue);
250                    } else if (InputType.BooleanValue.equals(param.getType())) {
251                        adaptedDoc.setPropertyValue(param.getSource(), new Boolean(xmlValue));
252                    } else if (Date.class.getSimpleName().equals(param.getType())) {
253                        SimpleDateFormat wordXMLDateFormat = new SimpleDateFormat(WORD_XML_DATE_FORMAT);
254                        try {
255                            adaptedDoc.setPropertyValue(param.getSource(), wordXMLDateFormat.parse(xmlValue));
256                        } catch (PropertyException | ParseException e) {
257                            throw new IOException(e);
258                        }
259                    }
260                } else {
261                    if (InputType.StringValue.equals(param.getType())) {
262                        param.setStringValue(xmlValue);
263                    } else if (InputType.BooleanValue.equals(param.getType())) {
264                        param.setBooleanValue(new Boolean(xmlValue));
265                    } else if (InputType.DateValue.equals(param.getType())) {
266                        SimpleDateFormat wordXMLDateFormat = new SimpleDateFormat(WORD_XML_DATE_FORMAT);
267                        try {
268                            param.setDateValue(wordXMLDateFormat.parse(xmlValue));
269                        } catch (ParseException e) {
270                            throw new IOException(e);
271                        }
272                    }
273                }
274            }
275        }
276        adaptedDoc = templateDocument.saveParams(templateName, params, false);
277        return adaptedDoc;
278    }
279
280}