source: trunk/docx4j/src/main/java/org/docx4j/openpackaging/io/SaveToZipFile.java @ 1648

Revision 1648, 15.0 KB checked in by jharrop, 9 months ago (diff)

getRelationshipsPart() will create it if it doesn't exist already, similar to behaviour of JAXB lists.

  • Property svn:eol-style set to native
Line 
1/*
2 *  Copyright 2007-2008, Plutext Pty Ltd.
3 *   
4 *  This file is part of docx4j.
5
6    docx4j is licensed under the Apache License, Version 2.0 (the "License");
7    you may not use this file except in compliance with the License.
8
9    You may obtain a copy of the License at
10
11        http://www.apache.org/licenses/LICENSE-2.0
12
13    Unless required by applicable law or agreed to in writing, software
14    distributed under the License is distributed on an "AS IS" BASIS,
15    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16    See the License for the specific language governing permissions and
17    limitations under the License.
18
19 */
20
21package org.docx4j.openpackaging.io;
22
23
24
25import java.io.FileNotFoundException;
26import java.io.FileOutputStream;
27import java.io.IOException;
28import java.io.OutputStream;
29import java.net.URI;
30import java.util.HashMap;
31import java.util.zip.ZipEntry;
32import java.util.zip.ZipOutputStream;
33
34import javax.xml.bind.JAXBContext;
35import javax.xml.bind.JAXBException;
36import javax.xml.bind.Marshaller;
37import javax.xml.bind.PropertyException;
38import javax.xml.transform.TransformerFactory;
39import javax.xml.transform.dom.DOMSource;
40import javax.xml.transform.stream.StreamResult;
41
42import org.apache.log4j.Logger;
43import org.docx4j.Docx4jProperties;
44import org.docx4j.convert.out.flatOpcXml.FlatOpcXmlCreator;
45import org.docx4j.docProps.core.CoreProperties;
46import org.docx4j.docProps.extended.Properties;
47import org.docx4j.jaxb.Context;
48import org.docx4j.jaxb.NamespacePrefixMapperUtils;
49import org.docx4j.openpackaging.URIHelper;
50import org.docx4j.openpackaging.contenttype.ContentTypeManager;
51import org.docx4j.openpackaging.exceptions.Docx4JException;
52import org.docx4j.openpackaging.packages.OpcPackage;
53import org.docx4j.openpackaging.parts.DocPropsCorePart;
54import org.docx4j.openpackaging.parts.DocPropsExtendedPart;
55import org.docx4j.openpackaging.parts.Part;
56import org.docx4j.openpackaging.parts.PartName;
57import org.docx4j.openpackaging.parts.WordprocessingML.BinaryPart;
58import org.docx4j.openpackaging.parts.relationships.Namespaces;
59import org.docx4j.openpackaging.parts.relationships.RelationshipsPart;
60import org.docx4j.relationships.Relationship;
61import org.w3c.dom.Document;
62
63
64/**
65 * Save a Package object to a Zip file or output stream
66 * @author jharrop
67 *
68 */
69public class SaveToZipFile {
70       
71        private static Logger log = Logger.getLogger(SaveToZipFile.class);                             
72       
73        public SaveToZipFile(OpcPackage p) {
74               
75                this.p = p;
76               
77        }
78               
79        // The package to save
80        public OpcPackage p;
81       
82        /**
83         * This HashMap is intended to prevent loops.
84         */
85        private HashMap<String, String> handled;
86
87        /* Save a Package as a Zip file in the file system */
88        public boolean save(String filepath) throws Docx4JException  {
89                log.info("SAVING to " +  filepath );           
90                try {
91                        if (filepath.toLowerCase().endsWith(".xml") ) {
92                                return saveFlatOPC(new FileOutputStream(filepath));
93                        } else return save(new FileOutputStream(filepath));
94                } catch (FileNotFoundException e) {
95                        throw new Docx4JException("Failed to save package to path " + filepath, e);
96                }
97        }
98
99        /* Save a Package as a Zip file in the file system */
100        public boolean save(java.io.File docxFile) throws Docx4JException  {
101                log.info("Saving to" +  docxFile.getPath() );           
102                try {
103                        if (docxFile.getPath().toLowerCase().endsWith(".xml") ) {
104                                return saveFlatOPC(new FileOutputStream(docxFile));
105                        } else return save(new FileOutputStream(docxFile));
106                } catch (FileNotFoundException e) {
107                        throw new Docx4JException("Failed to save package to path " + docxFile.getPath(), e);
108                }
109        }
110       
111        public boolean saveFlatOPC(OutputStream realOS) throws Docx4JException {
112               
113                try {
114                        FlatOpcXmlCreator worker = new FlatOpcXmlCreator(p);
115                        org.docx4j.xmlPackage.Package pkg = worker.get();
116                       
117                        // Now marshall it
118                        JAXBContext jc = Context.jcXmlPackage;
119                        Marshaller marshaller=jc.createMarshaller();
120                       
121                        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
122                        NamespacePrefixMapperUtils.setProperty(marshaller, 
123                                        NamespacePrefixMapperUtils.getPrefixMapper());                 
124                       
125                        marshaller.marshal(pkg, realOS);
126                        return true;
127                } catch (Exception e) {
128                        throw new Docx4JException("Failed to save Flat OPC ", e);
129                }                       
130               
131        }
132       
133        /* Save a Package as a Zip file in the outputstream provided */
134        public boolean save(OutputStream realOS) throws Docx4JException  {             
135                handled = new HashMap<String, String>();
136                 try {
137
138                        ZipOutputStream out = new ZipOutputStream(realOS);
139                       
140                       
141                        // 3. Save [Content_Types].xml
142                        ContentTypeManager ctm = p.getContentTypeManager();
143                out.putNextEntry(new ZipEntry("[Content_Types].xml"));
144                ctm.marshal(out);
145                out.closeEntry();
146               
147                        // 4. Start with _rels/.rels
148
149//                      <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
150//                        <Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/>
151//                        <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>
152//                        <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/>
153//                      </Relationships>               
154                       
155                        String partName = "_rels/.rels";
156                        RelationshipsPart rp = p.getRelationshipsPart();
157                        //deprecatedSaveRawXmlPart(out, partName, rp.getDocument() );
158                        // 2008 06 12 - try this neater method
159                        saveRawXmlPart(out, rp, partName );
160                       
161                       
162                        // 5. Now recursively
163//                      addPartsFromRelationships(out, "", rp );
164                        addPartsFromRelationships(out, rp );
165           
166                       
167                // Complete the ZIP file
168                        // Don't forget to do this or everything will appear
169                        // to work, but when you open the zip file you'll get an error
170                        // "End-of-central-directory signature not found."
171                out.close();
172                realOS.close();
173            } catch (Exception e) {
174                        e.printStackTrace() ;
175                        if (e instanceof Docx4JException) {
176                                throw (Docx4JException)e;
177                        } else {
178                                throw new Docx4JException("Failed to save package", e);
179                        }
180            }
181
182            log.info("...Done!" );             
183
184                 return true;
185        }
186
187
188        public void  saveRawXmlPart(ZipOutputStream out, Part part) throws Docx4JException {
189               
190                // This is a neater signature and should be used where possible!
191               
192                String partName = part.getPartName().getName().substring(1);
193
194                saveRawXmlPart(out, part, partName);
195        }
196       
197        public void  saveRawXmlPart(ZipOutputStream out, Part part, String zipEntryName) throws Docx4JException {
198               
199                try {
200                                               
201                        if (part instanceof org.docx4j.openpackaging.parts.JaxbXmlPart) {
202                               
203                                // From docx4j 2.7.1, support setting some basic metadata
204                                // from docx4j.properties
205                                if (part instanceof DocPropsCorePart) {
206                                       
207                                        boolean dcWrite= Boolean.parseBoolean(
208                                                        Docx4jProperties.getProperties().getProperty("docx4j.dc.write", "false"));
209                                        if (dcWrite) {                                 
210                                                CoreProperties cp = ((DocPropsCorePart)part).getJaxbElement();
211                                               
212                                                // Only set creator if not already present
213                                                String creator= Docx4jProperties.getProperties().getProperty("docx4j.dc.creator.value", "docx4j");
214                                                if (cp.getCreator()==null) {
215                                                        org.docx4j.docProps.core.dc.elements.ObjectFactory of = new org.docx4j.docProps.core.dc.elements.ObjectFactory(); 
216                                                        cp.setCreator(of.createSimpleLiteral() );
217                                                        cp.getCreator().getContent().add(creator);
218                                                } 
219                                               
220                                                String modifier= Docx4jProperties.getProperties().getProperty("docx4j.dc.lastModifiedBy.value", "docx4j");
221                                                cp.setLastModifiedBy(modifier);
222                                        }
223                                }                               
224                                if (part instanceof DocPropsExtendedPart) {
225                                        boolean appWrite= Boolean.parseBoolean(
226                                                        Docx4jProperties.getProperties().getProperty("docx4j.App.write", "false"));
227                                        if (appWrite) {
228                                                Properties cp = ((DocPropsExtendedPart)part).getJaxbElement();
229                                               
230                                                cp.setApplication( 
231                                                                Docx4jProperties.getProperties().getProperty("docx4j.Application", "docx4j")
232                                                                );
233                                               
234                                                String version = Docx4jProperties.getProperties().getProperty("docx4j.AppVersion");
235                                                if ( version!=null ) {
236                                                        cp.setAppVersion(version);
237                                                }
238                                               
239                                        }
240                                }
241
242                        // Add ZIP entry to output stream.
243                        out.putNextEntry(new ZipEntry(zipEntryName));                   
244
245                        ((org.docx4j.openpackaging.parts.JaxbXmlPart)part).marshal( out );
246                       
247                        // Complete the entry
248                        out.closeEntry();
249                                log.info( "success writing part: " +  zipEntryName);           
250                       
251
252                        } else if (part instanceof org.docx4j.openpackaging.parts.CustomXmlDataStoragePart) {
253
254                        // Add ZIP entry to output stream.
255                        out.putNextEntry(new ZipEntry(zipEntryName));                   
256
257                        ((org.docx4j.openpackaging.parts.CustomXmlDataStoragePart)part).getData().writeDocument( out );
258                       
259                        // Complete the entry
260                        out.closeEntry();
261                                log.info( "success writing part: " +  zipEntryName);           
262
263                        } else if (part instanceof org.docx4j.openpackaging.parts.XmlPart) {
264                               
265                        // Add ZIP entry to output stream.
266                        out.putNextEntry(new ZipEntry(zipEntryName));                   
267
268                       Document doc =  ((org.docx4j.openpackaging.parts.XmlPart)part).getDocument();
269                       
270                                /*
271                                 * With Crimson, this gives:
272                                 *
273                                        Exception in thread "main" java.lang.AbstractMethodError: org.apache.crimson.tree.XmlDocument.getXmlStandalone()Z
274                                                at com.sun.org.apache.xalan.internal.xsltc.trax.DOM2TO.setDocumentInfo(DOM2TO.java:373)
275                                                at com.sun.org.apache.xalan.internal.xsltc.trax.DOM2TO.parse(DOM2TO.java:127)
276                                                at com.sun.org.apache.xalan.internal.xsltc.trax.DOM2TO.parse(DOM2TO.java:94)
277                                                at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transformIdentity(TransformerImpl.java:662)
278                                                at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(TransformerImpl.java:708)
279                                                at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(TransformerImpl.java:313)
280                                                at org.docx4j.model.datastorage.CustomXmlDataStorageImpl.writeDocument(CustomXmlDataStorageImpl.java:174)
281                                 *
282                                 */
283                                DOMSource source = new DOMSource(doc);
284                                 TransformerFactory.newInstance().newTransformer().transform(source, 
285                                                 new StreamResult(out) );
286                       
287                       
288                        // Complete the entry
289                        out.closeEntry();
290                                log.info( "success writing part: " + zipEntryName);             
291                                               
292                        } else {
293                                // Shouldn't happen, since ContentTypeManagerImpl should
294                                // return an instance of one of the above, or throw an
295                                // Exception.
296                               
297                                log.error("PROBLEM - No suitable part found for: " + zipEntryName);
298                        }               
299               
300                } catch (Exception e) {
301                        e.printStackTrace();
302                        log.error(e);
303                        throw new Docx4JException("Problem saving part " + zipEntryName, e);
304                } 
305               
306        }
307       
308       
309        /* recursively
310                (i) get each Part listed in the relationships
311                (ii) add the Part to the zip file
312                (iii) traverse its relationship
313        */
314        public void addPartsFromRelationships(ZipOutputStream out,  RelationshipsPart rp )
315         throws Docx4JException {
316               
317//              for (Iterator it = rp.iterator(); it.hasNext(); ) {
318//                      Relationship r = (Relationship)it.next();
319//                      log.info("For Relationship Id=" + r.getId() + " Source is " + r.getSource().getPartName() + ", Target is " + r.getTargetURI() );
320                for ( Relationship r : rp.getRelationships().getRelationship() ) {
321                       
322                        log.debug("For Relationship Id=" + r.getId() 
323                                        + " Source is " + rp.getSourceP().getPartName() 
324                                        + ", Target is " + r.getTarget() );
325                       
326                        if (r.getType().equals(Namespaces.HYPERLINK)) {                         
327                                continue;  // whether internal or external                                                             
328                        }
329                       
330                        if (r.getTargetMode() != null
331                                        && r.getTargetMode().equals("External") ) {
332                               
333                                // ie its EXTERNAL
334                                // As at 1 May 2008, we don't have a Part for these;
335                                // there is just the relationship.
336
337                                log.warn("Encountered external resource " + r.getTarget() 
338                                                   + " of type " + r.getType() );
339                               
340                                // So
341                                continue;                               
342                        }
343                       
344                        try {
345                                //String resolvedPartUri = URIHelper.resolvePartUri(r.getSourceURI(), r.getTargetURI() ).toString();
346
347                                String resolvedPartUri = URIHelper.resolvePartUri(rp.getSourceURI(), new URI(r.getTarget() ) ).toString();             
348                               
349                                // Now drop leading "/'
350                                resolvedPartUri = resolvedPartUri.substring(1);                         
351                               
352                                // Now normalise it .. ie abc/def/../ghi
353                                // becomes abc/ghi
354                                // Maybe this isn't necessary with a zip file,
355                                // - ZipFile class may be smart enough to do it.
356                                // But it is certainly necessary in the JCR case.
357//                              target = (new java.net.URI(target)).normalize().toString();
358//                              log.info("Normalised, it is " + target );                               
359                               
360//                              Document contents = getDocumentFromZippedPart( zf,  target);
361                               
362                                if (!false) {
363                                        log.debug("Getting part /" + resolvedPartUri );
364                                       
365                                        Part part = p.getParts().get(new PartName("/" + resolvedPartUri));
366                                       
367                                        if (part==null) {
368                                                log.error("Part " + resolvedPartUri + " not found!");
369                                        } else {
370                                                log.debug(part.getClass().getName() );
371                                        }
372                                       
373                                        savePart(out, part);
374                                       
375                                }
376                                       
377                        } catch (Exception e) {
378                                throw new Docx4JException("Failed to add parts from relationships", e);                         
379                        }
380                }
381               
382               
383        }
384
385
386        /**
387         * @param out
388         * @param resolvedPartUri
389         * @param part
390         * @throws Docx4JException
391         * @throws IOException
392         */
393        public void savePart(ZipOutputStream out, Part part)
394                        throws Docx4JException, IOException {
395               
396                // Drop the leading '/'
397                String resolvedPartUri = part.getPartName().getName().substring(1);
398               
399                if (handled.get(resolvedPartUri)!=null) {
400                        log.debug(".. duplicate save avoided .." );
401                        return;
402                }
403                               
404                if (part instanceof BinaryPart ) {
405                        log.debug(".. saving binary stuff" );
406                        saveRawBinaryPart( out, part );
407                       
408                } else {
409                        log.debug(".. saving " );                                       
410                        saveRawXmlPart( out, part );
411                }
412                handled.put(resolvedPartUri, resolvedPartUri);
413               
414                // recurse via this parts relationships, if it has any
415                RelationshipsPart rrp = part.getRelationshipsPart(false); //don't create
416                if (rrp!= null ) {
417                       
418                        //log.debug("Found relationships " + rrp.getPartName() );
419                       
420                        // Only save it if it actually has rels in it
421                        if (rrp.getRelationships().getRelationship().size()>0) {
422                               
423                                String relPart = PartName.getRelationshipsPartName(resolvedPartUri);
424                                //log.debug("Cf constructed name " + relPart );
425                               
426                                saveRawXmlPart(out, rrp, relPart );
427                               
428                                addPartsFromRelationships(out, rrp );
429                        }
430                } 
431//              else {
432//                      log.debug("No relationships for " + resolvedPartUri );                                 
433//              }
434        }
435       
436        protected void saveRawBinaryPart(ZipOutputStream out, Part part) throws Docx4JException {
437
438                // Drop the leading '/'
439                String resolvedPartUri = part.getPartName().getName().substring(1);
440
441                try {
442                // Add ZIP entry to output stream.
443                out.putNextEntry(new ZipEntry(resolvedPartUri));
444                               
445            java.nio.ByteBuffer bb = ((BinaryPart)part).getBuffer();
446            byte[] bytes = null;
447            bytes = new byte[bb.limit()];
448            bb.get(bytes);             
449               
450                out.write( bytes );
451
452                        // Complete the entry
453                out.closeEntry();
454                       
455                } catch (Exception e ) {
456                        throw new Docx4JException("Failed to put binary part", e);                     
457                }
458               
459                log.info( "success writing part: " + resolvedPartUri);         
460               
461        }
462       
463       
464       
465}
Note: See TracBrowser for help on using the repository browser.