xref: /trunk/main/xmerge/java/xmerge/src/main/java/org/openoffice/xmerge/converter/xml/OfficeDocument.java (revision b6fd66b75b193ec5c149e1b2c838579d044a0b3d)
1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  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,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 package org.openoffice.xmerge.converter.xml;
25 
26 import java.io.InputStream;
27 import java.io.OutputStream;
28 import java.io.Reader;
29 import java.io.BufferedReader;
30 import java.io.StringReader;
31 import java.io.ByteArrayOutputStream;
32 import java.io.InputStreamReader;
33 import java.io.ByteArrayInputStream;
34 import java.io.IOException;
35 import java.util.Iterator;
36 import java.util.Map;
37 import java.util.HashMap;
38 
39 import javax.xml.parsers.DocumentBuilderFactory;
40 import javax.xml.parsers.DocumentBuilder;
41 import javax.xml.parsers.ParserConfigurationException;
42 
43 import org.w3c.dom.Node;
44 import org.w3c.dom.Element;
45 import org.w3c.dom.Document;
46 import org.w3c.dom.DOMImplementation;
47 import org.w3c.dom.DocumentType;
48 import org.w3c.dom.NodeList;
49 import org.xml.sax.InputSource;
50 import org.w3c.dom.NamedNodeMap;
51 import org.xml.sax.SAXException;
52 
53 import javax.xml.transform.*;
54 import javax.xml.transform.dom.*;
55 import javax.xml.transform.stream.*;
56 
57 import org.openoffice.xmerge.util.Resources;
58 import org.openoffice.xmerge.util.Debug;
59 
60 /**
61  *  An implementation of <code>Document</code> for
62  *  StarOffice documents.
63  */
64 public abstract class OfficeDocument
65     implements org.openoffice.xmerge.Document,
66                OfficeConstants {
67 
68     /** Factory for <code>DocumentBuilder</code> objects. */
69     private static DocumentBuilderFactory factory =
70        DocumentBuilderFactory.newInstance();
71 
72     /** DOM <code>Document</code> of content.xml. */
73     private Document contentDoc = null;
74 
75    /** DOM <code>Document</code> of meta.xml. */
76     private Document metaDoc = null;
77 
78    /** DOM <code>Document</code> of settings.xml. */
79     private Document settingsDoc = null;
80 
81     /** DOM <code>Document</code> of content.xml. */
82     private Document styleDoc = null;
83 
84     /** DOM <code>Docuemtn</code> of META-INF/manifest.xml. */
85     private Document manifestDoc = null;
86 
87     private String documentName = null;
88     private String fileName = null;
89 
90     /** Resources object. */
91     private Resources res = null;
92 
93     /**
94      *  <code>OfficeZip</code> object to store zip contents from
95      *  read <code>InputStream</code>.  Note that this member
96      *  will still be null if it was initialized using a template
97      *  file instead of reading from a StarOffice zipped
98      *  XML file.
99      */
100     private OfficeZip zip = null;
101 
102     /** Collection to keep track of the embedded objects in the document. */
103     private Map embeddedObjects = null;
104 
105     /**
106      *  Default constructor.
107      *
108      *  @param  name  <code>Document</code> name.
109      */
OfficeDocument(String name)110     public OfficeDocument(String name)
111     {
112         this(name, true, false);
113     }
114 
115 
116     /**
117      *  Constructor with arguments to set <code>namespaceAware</code>
118      *  and <code>validating</code> flags.
119      *
120      *  @param  name            <code>Document</code> name (may or may not
121      *                          contain extension).
122      *  @param  namespaceAware  Value for <code>namespaceAware</code> flag.
123      *  @param  validating      Value for <code>validating</code> flag.
124      */
OfficeDocument(String name, boolean namespaceAware, boolean validating)125     public OfficeDocument(String name, boolean namespaceAware, boolean validating) {
126 
127         res = Resources.getInstance();
128         factory.setValidating(validating);
129         factory.setNamespaceAware(namespaceAware);
130         this.documentName = trimDocumentName(name);
131         this.fileName = documentName + getFileExtension();
132     }
133 
134 
135     /**
136      *  Removes the file extension from the <code>Document</code>
137      *  name.
138      *
139      *  @param  name  Full <code>Document</code> name with extension.
140      *
141      *  @return  Name of <code>Document</code> without the extension.
142      */
trimDocumentName(String name)143     private String trimDocumentName(String name) {
144         String temp = name.toLowerCase();
145         String ext = getFileExtension();
146 
147         if (temp.endsWith(ext)) {
148             // strip the extension
149             int nlen = name.length();
150             int endIndex = nlen - ext.length();
151             name = name.substring(0,endIndex);
152         }
153 
154         return name;
155     }
156 
157 
158     /**
159      *  Return a DOM <code>Document</code> object of the content.xml
160      *  file.  Note that a content DOM is not created when the constructor
161      *  is called.  So, either the <code>read</code> method or the
162      *  <code>initContentDOM</code> method will need to be called ahead
163      *  on this object before calling this method.
164      *
165      *  @return  DOM <code>Document</code> object.
166      */
getContentDOM()167     public Document getContentDOM() {
168 
169         return contentDoc;
170     }
171 
172  /**
173      *  Return a DOM <code>Document</code> object of the meta.xml
174      *  file.  Note that a content DOM is not created when the constructor
175      *  is called.  So, either the <code>read</code> method or the
176      *  <code>initContentDOM</code> method will need to be called ahead
177      *  on this object before calling this method.
178      *
179      *  @return  DOM <code>Document</code> object.
180      */
getMetaDOM()181     public Document getMetaDOM() {
182 
183         return metaDoc;
184     }
185 
186 
187  /**
188      *  Return a DOM <code>Document</code> object of the settings.xml
189      *  file.  Note that a content DOM is not created when the constructor
190      *  is called.  So, either the <code>read</code> method or the
191      *  <code>initContentDOM</code> method will need to be called ahead
192      *  on this object before calling this method.
193      *
194      *  @return  DOM <code>Document</code> object.
195      */
getSettingsDOM()196     public Document getSettingsDOM() {
197 
198         return settingsDoc;
199     }
200 
201 
202     /**
203      * Sets the content tree of the document.
204      *
205      * @param   newDom  <code>Node</code> containing the new content tree.
206      */
setContentDOM( Node newDom)207     public void setContentDOM( Node newDom) {
208         contentDoc = (Document)newDom;
209     }
210 
211 
212     /**
213      * Sets the meta tree of the document.
214      *
215      * @param   newDom  <code>Node</code> containing the new meta tree.
216      */
setMetaDOM(Node newDom)217     public void setMetaDOM (Node newDom) {
218         metaDoc = (Document)newDom;
219     }
220 
221 
222     /**
223      * Sets the settings tree of the document.
224      *
225      * @param   newDom  <code>Node</code> containing the new settings tree.
226      */
setSettingsDOM(Node newDom)227     public void setSettingsDOM (Node newDom) {
228         settingsDoc = (Document)newDom;
229     }
230 
231 
232     /**
233      * Sets the style tree of the document.
234      *
235      * @param   newDom  <code>Node</code> containing the new style tree.
236      */
setStyleDOM(Node newDom)237     public void setStyleDOM (Node newDom) {
238         styleDoc = (Document)newDom;
239     }
240 
241 
242     /**
243      *  Return a DOM <code>Document</code> object of the style.xml file.
244      *  Note that this may return null if there is no style DOM.
245      *  Note that a style DOM is not created when the constructor
246      *  is called.  Depending on the <code>InputStream</code>, a
247      *  <code>read</code> method may or may not build a style DOM.  When
248      *  creating a new style DOM, call the <code>initStyleDOM</code> method
249      *  first.
250      *
251      *  @return  DOM <code>Document</code> object.
252      */
getStyleDOM()253     public Document getStyleDOM() {
254 
255         return styleDoc;
256     }
257 
258 
259     /**
260      *  Return the name of the <code>Document</code>.
261      *
262      *  @return  The name of <code>Document</code>.
263      */
getName()264     public String getName() {
265 
266         return documentName;
267     }
268 
269 
270     /**
271      *  Return the file name of the <code>Document</code>, possibly
272      *  with the standard extension.
273      *
274      *  @return  The file name of <code>Document</code>.
275      */
getFileName()276     public String getFileName() {
277 
278         return fileName;
279     }
280 
281 
282     /**
283      *  Returns the file extension for this type of
284      *  <code>Document</code>.
285      *
286      *  @return  The file extension of <code>Document</code>.
287      */
getFileExtension()288     protected abstract String getFileExtension();
289 
290 
291     /**
292      * Returns all the embedded objects (graphics, formulae, etc.) present in
293      * this document.
294      *
295      * @return An <code>Iterator</code> of <code>EmbeddedObject</code> objects.
296      */
getEmbeddedObjects()297     public Iterator getEmbeddedObjects() {
298 
299         if (embeddedObjects == null && manifestDoc != null) {
300             embeddedObjects = new HashMap();
301 
302             // Need to read the manifest file and construct a list of objects
303             NodeList nl = manifestDoc.getElementsByTagName(TAG_MANIFEST_FILE);
304 
305             // Dont create the HashMap if there are no embedded objects
306             int len = nl.getLength();
307             for (int i = 0; i < len; i++) {
308                 Node n = nl.item(i);
309 
310                 NamedNodeMap attrs = n.getAttributes();
311 
312                 String type = attrs.getNamedItem(ATTRIBUTE_MANIFEST_FILE_TYPE).getNodeValue();
313                 String path = attrs.getNamedItem(ATTRIBUTE_MANIFEST_FILE_PATH).getNodeValue();
314 
315 
316                 /*
317                  * According to OpenOffice.org XML File Format document (ver. 1)
318                  * there are only two types of embedded object:
319                  *
320                  *      Objects with an XML representation.
321                  *      Objects without an XML representation.
322                  *
323                  * The former are represented by one or more XML files.
324                  * The latter are in binary form.
325                  */
326                 if (type.startsWith("application/vnd.sun.xml"))
327                 {
328                     if (path.equals("/")) {
329                         // Exclude the main document entries
330                         continue;
331                     }
332                     // Take off the trailing '/'
333                     String name = path.substring(0, path.length() - 1);
334                     embeddedObjects.put(name, new EmbeddedXMLObject(name, type, zip));
335                 }
336                 else if (type.equals("text/xml")) {
337                     // XML entries are either embedded StarOffice doc entries or main
338                     // document entries
339                     continue;
340                 }
341                 else { // FIX (HJ): allows empty MIME type
342                     embeddedObjects.put(path, new EmbeddedBinaryObject(path, type, zip));
343                 }
344             }
345         }
346 
347         return embeddedObjects.values().iterator();
348     }
349 
350     /**
351      * Returns the embedded object corresponding to the name provided.
352      * The name should be stripped of any preceding path characters, such as
353      * '/', '.' or '#'.
354      *
355      * @param   name    The name of the embedded object to retrieve.
356      *
357      * @return  An <code>EmbeddedObject</code> instance representing the named
358      *          object.
359      */
getEmbeddedObject(String name)360     public EmbeddedObject getEmbeddedObject(String name) {
361         if (name == null) {
362             return null;
363         }
364 
365         if (embeddedObjects == null) {
366             getEmbeddedObjects();
367         }
368 
369         if (embeddedObjects.containsKey(name)) {
370             return (EmbeddedObject)embeddedObjects.get(name);
371         }
372         else {
373             return null;
374         }
375     }
376 
377 
378     /**
379      * Adds a new embedded object to the document.
380      *
381      * @param   embObj  An instance of <code>EmbeddedObject</code>.
382      */
addEmbeddedObject(EmbeddedObject embObj)383     public void addEmbeddedObject(EmbeddedObject embObj) {
384         if (embObj == null) {
385             return;
386         }
387 
388         if (embeddedObjects == null) {
389             embeddedObjects = new HashMap();
390         }
391 
392         embeddedObjects.put(embObj.getName(), embObj);
393     }
394 
395 
396     /**
397      *  Read the Office <code>Document</code> from the given
398      *  <code>InputStream</code>.
399      *
400      *  @param  is  Office document <code>InputStream</code>.
401      *
402      *  @throws  IOException  If any I/O error occurs.
403      */
read(InputStream is)404     public void read(InputStream is) throws IOException {
405 
406         Debug.log(Debug.INFO, "reading Office file");
407 
408         DocumentBuilder builder = null;
409 
410         try {
411             builder = factory.newDocumentBuilder();
412         } catch (ParserConfigurationException ex) {
413             throw new OfficeDocumentException(ex);
414         }
415 
416         // read in Office zip file format
417 
418         zip = new OfficeZip();
419         zip.read(is);
420 
421         // grab the content.xml and
422         // parse it into contentDoc.
423 
424         byte contentBytes[] = zip.getContentXMLBytes();
425 
426         if (contentBytes == null) {
427 
428             throw new OfficeDocumentException("Entry content.xml not found in file");
429         }
430 
431         try {
432 
433             contentDoc = parse(builder, contentBytes);
434 
435         } catch (SAXException ex) {
436 
437             throw new OfficeDocumentException(ex);
438         }
439 
440         // if style.xml exists, grab the style.xml
441         // parse it into styleDoc.
442 
443         byte styleBytes[] = zip.getStyleXMLBytes();
444 
445         if (styleBytes != null) {
446 
447             try {
448 
449                 styleDoc = parse(builder, styleBytes);
450 
451             } catch (SAXException ex) {
452 
453                 throw new OfficeDocumentException(ex);
454             }
455         }
456 
457     byte metaBytes[] = zip.getMetaXMLBytes();
458 
459         if (metaBytes != null) {
460 
461             try {
462 
463                 metaDoc = parse(builder, metaBytes);
464 
465             } catch (SAXException ex) {
466 
467                 throw new OfficeDocumentException(ex);
468             }
469         }
470 
471     byte settingsBytes[] = zip.getSettingsXMLBytes();
472 
473         if (settingsBytes != null) {
474 
475             try {
476 
477                 settingsDoc = parse(builder, settingsBytes);
478 
479             } catch (SAXException ex) {
480 
481                 throw new OfficeDocumentException(ex);
482             }
483         }
484 
485 
486         // Read in the META-INF/manifest.xml file
487         byte manifestBytes[] = zip.getManifestXMLBytes();
488 
489         if (manifestBytes != null) {
490 
491             try {
492                 manifestDoc = parse(builder, manifestBytes);
493             } catch (SAXException ex) {
494                 throw new OfficeDocumentException(ex);
495             }
496         }
497 
498     }
499 
500 
501     /**
502      *  Read the Office <code>Document</code> from the given
503      *  <code>InputStream</code>.
504      *
505      *  @param  is  Office document <code>InputStream</code>.
506      *  @param  isZip <code>boolean</code> Identifies whether
507      *                 a file is zipped or not
508      *
509      *  @throws  IOException  If any I/O error occurs.
510      */
read(InputStream is, boolean isZip)511     public void read(InputStream is, boolean isZip) throws IOException {
512 
513         Debug.log(Debug.INFO, "reading Office file");
514 
515         DocumentBuilder builder = null;
516 
517         try {
518             builder = factory.newDocumentBuilder();
519         } catch (ParserConfigurationException ex) {
520             throw new OfficeDocumentException(ex);
521         }
522 
523     if (isZip)
524     {
525             read(is);
526     }
527     else{
528         try{
529         //System.out.println("\nParsing Input stream, validating?: "+builder.isValidating());
530         //contentDoc=  builder.parse((InputStream)is);
531 
532                Reader r = secondHack(is);
533                InputSource ins = new InputSource(r);
534             org.w3c.dom.Document newDoc = builder.parse(ins);
535             //org.w3c.dom.Document newDoc = builder.parse((InputStream)is);
536             Element rootElement=newDoc.getDocumentElement();
537 
538             NodeList nodeList;
539             Node tmpNode;
540             Node rootNode = (Node)rootElement;
541                 if (newDoc !=null){
542             /*content*/
543                    contentDoc = createDOM(TAG_OFFICE_DOCUMENT_CONTENT);
544                    rootElement=contentDoc.getDocumentElement();
545                    rootNode = (Node)rootElement;
546 
547                    // FIX (HJ): Include office:font-decls in content DOM
548                    nodeList= newDoc.getElementsByTagName(TAG_OFFICE_FONT_DECLS);
549                    if (nodeList.getLength()>0){
550                        tmpNode = contentDoc.importNode(nodeList.item(0),true);
551                        rootNode.appendChild(tmpNode);
552                    }
553 
554                    nodeList= newDoc.getElementsByTagName(TAG_OFFICE_AUTOMATIC_STYLES);
555                    if (nodeList.getLength()>0){
556                   tmpNode = contentDoc.importNode(nodeList.item(0),true);
557                   rootNode.appendChild(tmpNode);
558                    }
559 
560                     nodeList= newDoc.getElementsByTagName(TAG_OFFICE_BODY);
561                    if (nodeList.getLength()>0){
562                   tmpNode = contentDoc.importNode(nodeList.item(0),true);
563                   rootNode.appendChild(tmpNode);
564                    }
565 
566            /*Styles*/
567                    styleDoc = createDOM(TAG_OFFICE_DOCUMENT_STYLES);
568                    rootElement=styleDoc.getDocumentElement();
569                    rootNode = (Node)rootElement;
570 
571                    // FIX (HJ): Include office:font-decls in styles DOM
572                    nodeList= newDoc.getElementsByTagName(TAG_OFFICE_FONT_DECLS);
573                    if (nodeList.getLength()>0){
574                       tmpNode = styleDoc.importNode(nodeList.item(0),true);
575                       rootNode.appendChild(tmpNode);
576                    }
577 
578                    nodeList= newDoc.getElementsByTagName(TAG_OFFICE_STYLES);
579                    if (nodeList.getLength()>0){
580                   tmpNode = styleDoc.importNode(nodeList.item(0),true);
581                   rootNode.appendChild(tmpNode);
582                    }
583 
584                    // FIX (HJ): Include office:automatic-styles in styles DOM
585                    nodeList= newDoc.getElementsByTagName(TAG_OFFICE_AUTOMATIC_STYLES);
586                    if (nodeList.getLength()>0){
587                       tmpNode = styleDoc.importNode(nodeList.item(0),true);
588                       rootNode.appendChild(tmpNode);
589                    }
590 
591                    // FIX (HJ): Include office:master-styles in styles DOM
592                    nodeList= newDoc.getElementsByTagName(TAG_OFFICE_MASTER_STYLES);
593                    if (nodeList.getLength()>0){
594                        tmpNode = styleDoc.importNode(nodeList.item(0),true);
595                        rootNode.appendChild(tmpNode);
596                    }
597 
598            /*Settings*/
599                    settingsDoc = createDOM(TAG_OFFICE_DOCUMENT_SETTINGS);
600                    rootElement=settingsDoc.getDocumentElement();
601                    rootNode = (Node)rootElement;
602                    nodeList= newDoc.getElementsByTagName(TAG_OFFICE_SETTINGS);
603                    if (nodeList.getLength()>0){
604                   tmpNode = settingsDoc.importNode(nodeList.item(0),true);
605                   rootNode.appendChild(tmpNode);
606                    }
607            /*Meta*/
608                    metaDoc = createDOM(TAG_OFFICE_DOCUMENT_META);
609                    rootElement=metaDoc.getDocumentElement();
610                    rootNode = (Node)rootElement;
611                    nodeList= newDoc.getElementsByTagName(TAG_OFFICE_META);
612                    if (nodeList.getLength()>0){
613                   tmpNode = metaDoc.importNode(nodeList.item(0),true);
614                   rootNode.appendChild(tmpNode);
615                    }
616                 }
617         }
618         catch (SAXException ex) {
619         throw new OfficeDocumentException(ex);
620         }
621     }
622 
623     }
624 
625 
626 
627     /**
628      *  Parse given <code>byte</code> array into a DOM
629      *  <code>Document</code> object using the
630      *  <code>DocumentBuilder</code> object.
631      *
632      *  @param  builder  <code>DocumentBuilder</code> object for parsing.
633      *  @param  bytes    <code>byte</code> array for parsing.
634      *
635      *  @return  Resulting DOM <code>Document</code> object.
636      *
637      *  @throws  SAXException  If any parsing error occurs.
638      */
parse(DocumentBuilder builder, byte bytes[])639     static Document parse(DocumentBuilder builder, byte bytes[])
640         throws SAXException, IOException {
641 
642         Document doc = null;
643 
644         ByteArrayInputStream is = new ByteArrayInputStream(bytes);
645 
646         // TODO:  replace hack with a more appropriate fix.
647 
648         Reader r = hack(is);
649         InputSource ins = new InputSource(r);
650         doc = builder.parse(ins);
651 
652         return doc;
653     }
654 
655 
656     /**
657      * Method to return the MIME type of the document.
658      *
659      * @return  String  The document's MIME type.
660      */
getDocumentMimeType()661     protected abstract String getDocumentMimeType();
662 
663 
664     /**
665      *  Write out Office ZIP file format.
666      *
667      *  @param  os  XML <code>OutputStream</code>.
668      *
669      *  @throws  IOException  If any I/O error occurs.
670      */
write(OutputStream os)671     public void write(OutputStream os) throws IOException {
672         if (zip == null) {
673             zip = new OfficeZip();
674         }
675 
676         initManifestDOM();
677 
678         Element domEntry;
679         Element manifestRoot = manifestDoc.getDocumentElement();
680 
681         // The EmbeddedObjects come first.
682         Iterator embObjs = getEmbeddedObjects();
683         while (embObjs.hasNext()) {
684             EmbeddedObject obj = (EmbeddedObject)embObjs.next();
685             obj.writeManifestData(manifestDoc);
686 
687             obj.write(zip);
688         }
689 
690         // Add in the entry for the Pictures directory.  Always present.
691         domEntry = manifestDoc.createElement(TAG_MANIFEST_FILE);
692         domEntry.setAttribute(ATTRIBUTE_MANIFEST_FILE_PATH, "Pictures/");
693         domEntry.setAttribute(ATTRIBUTE_MANIFEST_FILE_TYPE, "");
694         manifestRoot.appendChild(domEntry);
695 
696     // Write content to the Zip file and then write any of the optional
697         // data, if it exists.
698     zip.setContentXMLBytes(docToBytes(contentDoc));
699 
700         domEntry = manifestDoc.createElement(TAG_MANIFEST_FILE);
701         domEntry.setAttribute(ATTRIBUTE_MANIFEST_FILE_PATH, "content.xml");
702         domEntry.setAttribute(ATTRIBUTE_MANIFEST_FILE_TYPE, "text/xml");
703 
704         manifestRoot.appendChild(domEntry);
705 
706     if (styleDoc != null) {
707             zip.setStyleXMLBytes(docToBytes(styleDoc));
708 
709             domEntry = manifestDoc.createElement(TAG_MANIFEST_FILE);
710             domEntry.setAttribute(ATTRIBUTE_MANIFEST_FILE_PATH, "styles.xml");
711             domEntry.setAttribute(ATTRIBUTE_MANIFEST_FILE_TYPE, "text/xml");
712             manifestRoot.appendChild(domEntry);
713         }
714 
715         if (metaDoc != null) {
716             zip.setMetaXMLBytes(docToBytes(metaDoc));
717 
718             domEntry = manifestDoc.createElement(TAG_MANIFEST_FILE);
719             domEntry.setAttribute(ATTRIBUTE_MANIFEST_FILE_PATH, "meta.xml");
720             domEntry.setAttribute(ATTRIBUTE_MANIFEST_FILE_TYPE, "text/xml");
721             manifestRoot.appendChild(domEntry);
722         }
723 
724         if (settingsDoc != null) {
725             zip.setSettingsXMLBytes(docToBytes(settingsDoc));
726 
727             domEntry = manifestDoc.createElement(TAG_MANIFEST_FILE);
728             domEntry.setAttribute(ATTRIBUTE_MANIFEST_FILE_PATH, "settings.xml");
729             domEntry.setAttribute(ATTRIBUTE_MANIFEST_FILE_TYPE, "text/xml");
730             manifestRoot.appendChild(domEntry);
731         }
732 
733         zip.setManifestXMLBytes(docToBytes(manifestDoc));
734 
735         zip.write(os);
736     }
737 
738 
739      /**
740      *  Write out Office ZIP file format.
741      *
742      *  @param  os  XML <code>OutputStream</code>.
743      *  @param  isZip <code>boolean</code>
744      *
745      *  @throws  IOException  If any I/O error occurs.
746      */
write(OutputStream os, boolean isZip)747     public void write(OutputStream os, boolean isZip) throws IOException {
748 
749         // Create an OfficeZip object if one does not exist.
750         if (isZip){
751             write(os);
752     }
753     else{
754         try{
755         DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
756         DocumentBuilder builder= builderFactory.newDocumentBuilder();
757         DOMImplementation domImpl = builder.getDOMImplementation();
758         DocumentType docType =domImpl.createDocumentType("office:document","-//OpenOffice.org//DTD OfficeDocument 1.0//EN",null);
759         org.w3c.dom.Document newDoc = domImpl.createDocument("http://openoffice.org/2000/office","office:document",null);
760 
761 
762         Element rootElement=newDoc.getDocumentElement();
763         rootElement.setAttribute("xmlns:office","http://openoffice.org/2000/office");
764         rootElement.setAttribute("xmlns:style","http://openoffice.org/2000/style" );
765         rootElement.setAttribute("xmlns:text","http://openoffice.org/2000/text");
766         rootElement.setAttribute("xmlns:table","http://openoffice.org/2000/table");
767 
768         rootElement.setAttribute("xmlns:draw","http://openoffice.org/2000/drawing");
769         rootElement.setAttribute("xmlns:fo","http://www.w3.org/1999/XSL/Format" );
770         rootElement.setAttribute("xmlns:xlink","http://www.w3.org/1999/xlink" );
771         rootElement.setAttribute("xmlns:dc","http://purl.org/dc/elements/1.1/" );
772         rootElement.setAttribute("xmlns:meta","http://openoffice.org/2000/meta" );
773         rootElement.setAttribute("xmlns:number","http://openoffice.org/2000/datastyle" );
774         rootElement.setAttribute("xmlns:svg","http://www.w3.org/2000/svg" );
775         rootElement.setAttribute("xmlns:chart","http://openoffice.org/2000/chart" );
776         rootElement.setAttribute("xmlns:dr3d","http://openoffice.org/2000/dr3d" );
777         rootElement.setAttribute("xmlns:math","http://www.w3.org/1998/Math/MathML" );
778         rootElement.setAttribute("xmlns:form","http://openoffice.org/2000/form" );
779         rootElement.setAttribute("xmlns:script","http://openoffice.org/2000/script" );
780         rootElement.setAttribute("xmlns:config","http://openoffice.org/2001/config" );
781         // #i41033# OASIS format needs the "office:class" set.
782         if(getDocumentMimeType() == SXC_MIME_TYPE)
783             rootElement.setAttribute("office:class","spreadsheet" );
784         else if(getDocumentMimeType() == SXW_MIME_TYPE)
785             rootElement.setAttribute("office:class","text" );
786         rootElement.setAttribute("office:version","1.0");
787 
788 
789         NodeList nodeList;
790         Node tmpNode;
791         Node rootNode = (Node)rootElement;
792         if (metaDoc !=null){
793             nodeList= metaDoc.getElementsByTagName(TAG_OFFICE_META);
794             if (nodeList.getLength()>0){
795             tmpNode = newDoc.importNode(nodeList.item(0),true);
796             rootNode.appendChild(tmpNode);
797             }
798         }if (styleDoc !=null){
799             nodeList= styleDoc.getElementsByTagName(TAG_OFFICE_STYLES);
800             if (nodeList.getLength()>0){
801             tmpNode = newDoc.importNode(nodeList.item(0),true);
802             rootNode.appendChild(tmpNode);
803                 }
804 
805         }if (settingsDoc !=null){
806             nodeList= settingsDoc.getElementsByTagName(TAG_OFFICE_SETTINGS);
807             if (nodeList.getLength()>0){
808             tmpNode = newDoc.importNode(nodeList.item(0),true);
809             rootNode.appendChild(tmpNode);
810             }
811         }
812         if (contentDoc !=null){
813             nodeList= contentDoc.getElementsByTagName(TAG_OFFICE_AUTOMATIC_STYLES);
814             if (nodeList.getLength()>0){
815             tmpNode = newDoc.importNode(nodeList.item(0),true);
816                  rootNode.appendChild(tmpNode);
817             }
818 
819             nodeList= contentDoc.getElementsByTagName(TAG_OFFICE_BODY);
820             if (nodeList.getLength()>0){
821             tmpNode = newDoc.importNode(nodeList.item(0),true);
822             rootNode.appendChild(tmpNode);
823             }
824         }
825 
826         byte contentBytes[] = docToBytes(newDoc);
827         //System.out.println(new String(contentBytes));
828         os.write(contentBytes);
829             }
830             catch(Exception exc){
831         System.out.println("\nException in OfficeDocument.write():" +exc);
832             }
833         //byte contentBytes[] = docToBytes(contentDoc);
834     }
835     }
836 
837 
838     /**
839      *  <p>Write out a <code>org.w3c.dom.Document</code> object into a
840      *  <code>byte</code> array.</p>
841      *
842      *  @param  doc  DOM <code>Document</code> object.
843      *
844      *  @return  <code>byte</code> array of DOM <code>Document</code>
845      *           object.
846      *
847      *  @throws  IOException  If any I/O error occurs.
848      */
docToBytes(Document doc)849     static byte[] docToBytes(Document doc)
850         throws IOException {
851         try {
852             DOMSource domSource = new DOMSource(doc);
853             ByteArrayOutputStream output = new ByteArrayOutputStream();
854             StreamResult result = new StreamResult(output);
855             TransformerFactory tf = TransformerFactory.newInstance();
856             Transformer transformer = tf.newTransformer();
857             transformer.transform(domSource, result);
858             return output.toByteArray();
859         }
860         catch (Exception e) {
861             throw new IOException("Unable to serialize XML document: " + e);
862         }
863     }
864 
865 
866     /**
867      *  Initializes a new DOM <code>Document</code> with the content
868      *  containing minimum OpenOffice XML tags.
869      *
870      *  @throws  IOException  If any I/O error occurs.
871      */
initContentDOM()872     public final void initContentDOM() throws IOException {
873 
874         contentDoc = createDOM(TAG_OFFICE_DOCUMENT_CONTENT);
875 
876         // this is a work-around for a bug in Office6.0 - not really
877         // needed but StarCalc 6.0 will crash without this tag.
878         Element root = contentDoc.getDocumentElement();
879 
880         Element child = contentDoc.createElement(TAG_OFFICE_FONT_DECLS);
881         root.appendChild(child);
882 
883         child = contentDoc.createElement(TAG_OFFICE_AUTOMATIC_STYLES);
884         root.appendChild(child);
885 
886         child = contentDoc.createElement(TAG_OFFICE_BODY);
887         root.appendChild(child);
888     }
889 
890     /**
891      *  Initializes a new DOM <code>Document</code> with the content
892      *  containing minimum OpenOffice XML tags.
893      *
894      *  @throws  IOException  If any I/O error occurs.
895      */
initSettingsDOM()896     public final void initSettingsDOM() throws IOException {
897 
898         settingsDoc = createSettingsDOM(TAG_OFFICE_DOCUMENT_SETTINGS);
899 
900         // this is a work-around for a bug in Office6.0 - not really
901         // needed but StarCalc 6.0 will crash without this tag.
902         Element root = settingsDoc.getDocumentElement();
903 
904         Element child = settingsDoc.createElement(TAG_OFFICE_SETTINGS);
905         root.appendChild(child);
906     }
907 
908     /**
909      *  Initializes a new DOM Document with styles
910      *  containing minimum OpenOffice XML tags.
911      *
912      *  @throws  IOException  If any I/O error occurs.
913      */
initStyleDOM()914     public final void initStyleDOM() throws IOException {
915 
916         styleDoc = createDOM(TAG_OFFICE_DOCUMENT_STYLES);
917     }
918 
919     /**
920      *  <p>Creates a new DOM <code>Document</code> containing minimum
921      *  OpenOffice.org XML tags.</p>
922      *
923      *  <p>This method uses the subclass
924      *  <code>getOfficeClassAttribute</code> method to get the
925      *  attribute for <i>office:class</i>.</p>
926      *
927      *  @param  rootName  root name of <code>Document</code>.
928      *
929      *  @throws  IOException  If any I/O error occurs.
930      */
createSettingsDOM(String rootName)931     private final Document createSettingsDOM(String rootName) throws IOException {
932 
933         Document doc = null;
934 
935         try {
936 
937             DocumentBuilder builder = factory.newDocumentBuilder();
938             doc = builder.newDocument();
939 
940         } catch (ParserConfigurationException ex) {
941 
942             throw new OfficeDocumentException(ex);
943 
944         }
945 
946         Element root = (Element) doc.createElement(rootName);
947         doc.appendChild(root);
948 
949         root.setAttribute("xmlns:office", "http://openoffice.org/2000/office");
950         root.setAttribute("xmlns:xlink", "http://openoffice.org/1999/xlink");
951         root.setAttribute("xmlns:config", "http://openoffice.org/2001/config");
952         root.setAttribute("office:version", "1.0");
953 
954         return doc;
955     }
956 
957 
958     /**
959      *  <p>Creates a new DOM <code>Document</code> containing minimum
960      *  OpenOffice.org XML tags.</p>
961      *
962      *  <p>This method uses the subclass
963      *  <code>getOfficeClassAttribute</code> method to get the
964      *  attribute for <i>office:class</i>.</p>
965      *
966      *  @param  rootName  root name of <code>Document</code>.
967      *
968      *  @throws  IOException  If any I/O error occurs.
969      */
createDOM(String rootName)970     private final Document createDOM(String rootName) throws IOException {
971 
972         Document doc = null;
973 
974         try {
975 
976             DocumentBuilder builder = factory.newDocumentBuilder();
977             doc = builder.newDocument();
978 
979         } catch (ParserConfigurationException ex) {
980 
981             throw new OfficeDocumentException(ex);
982 
983         }
984 
985         Element root = (Element) doc.createElement(rootName);
986         doc.appendChild(root);
987 
988         root.setAttribute("xmlns:office", "http://openoffice.org/2000/office");
989         root.setAttribute("xmlns:style", "http://openoffice.org/2000/style");
990         root.setAttribute("xmlns:text", "http://openoffice.org/2000/text");
991         root.setAttribute("xmlns:table", "http://openoffice.org/2000/table");
992         root.setAttribute("xmlns:draw", "http://openoffice.org/2000/drawing");
993         root.setAttribute("xmlns:fo", "http://www.w3.org/1999/XSL/Format");
994         root.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
995         root.setAttribute("xmlns:number", "http://openoffice.org/2000/datastyle");
996         root.setAttribute("xmlns:svg", "http://www.w3.org/2000/svg");
997         root.setAttribute("xmlns:chart", "http://openoffice.org/2000/chart");
998         root.setAttribute("xmlns:dr3d", "http://openoffice.org/2000/dr3d");
999         root.setAttribute("xmlns:math", "http://www.w3.org/1998/Math/MathML");
1000         root.setAttribute("xmlns:form", "http://openoffice.org/2000/form");
1001         root.setAttribute("xmlns:script", "http://openoffice.org/2000/script");
1002         root.setAttribute("office:class", getOfficeClassAttribute());
1003         root.setAttribute("office:version", "1.0");
1004 
1005         return doc;
1006     }
1007 
1008 
1009     /**
1010      *  Return the <i>office:class</i> attribute value.
1011      *
1012      *  @return  The attribute value.
1013      */
getOfficeClassAttribute()1014     protected abstract String getOfficeClassAttribute();
1015 
1016 
1017     /**
1018      *  <p>Hacked code to filter &lt;!DOCTYPE&gt; tag before
1019      *  sending stream to parser.</p>
1020      *
1021      *  <p>This hacked code needs to be changed later on.</p>
1022      *
1023      *  <p>Issue: using current jaxp1.0 parser, there is no way
1024      *  to turn off processing of dtds.  Current set of dtds
1025      *  have bugs, processing them will throw exceptions.</p>
1026      *
1027      *  <p>This is a simple hack that assumes the whole &lt;!DOCTYPE&gt;
1028      *  tag are all in the same line.  This is sufficient for
1029      *  current StarOffice 6.0 generated XML files.  Since this
1030      *  hack really needs to go away, I don't want to spend
1031      *  too much time in making it a perfect hack.</p>
1032      *  FIX (HJ): Removed requirement for DOCTYPE to be in one line
1033      *  FIX (HJ): No longer removes newlines
1034      *
1035      *  @param  is  <code>InputStream</code> to be filtered.
1036      *
1037      *  @return  Reader value without the &lt;!DOCTYPE&gt; tag.
1038      *
1039      *  @throws  IOException  If any I/O error occurs.
1040      */
hack(InputStream is)1041     private static Reader hack(InputStream is) throws IOException {
1042 
1043         BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
1044         StringBuilder buffer = new StringBuilder();
1045 
1046         String str = null;
1047 
1048         while ((str = br.readLine()) != null) {
1049 
1050             int sIndex = str.indexOf("<!DOCTYPE");
1051 
1052             if (sIndex > -1) {
1053 
1054                 buffer.append(str.substring(0, sIndex));
1055 
1056                 int eIndex = str.indexOf('>', sIndex + 8 );
1057 
1058                 if (eIndex > -1) {
1059 
1060                     buffer.append(str.substring(eIndex + 1, str.length()));
1061                     // FIX (HJ): Preserve the newline
1062                     buffer.append("\n");
1063 
1064                 } else {
1065 
1066                     // FIX (HJ): More than one line. Search for '>' in following lines
1067                     boolean bOK = false;
1068                     while ((str = br.readLine())!=null) {
1069                         eIndex = str.indexOf('>');
1070                         if (eIndex>-1) {
1071                             buffer.append(str.substring(eIndex+1));
1072                             // FIX (HJ): Preserve the newline
1073                             buffer.append("\n");
1074                             bOK = true;
1075                             break;
1076                         }
1077                     }
1078 
1079                     if (!bOK) { throw new IOException("Invalid XML"); }
1080                 }
1081 
1082             } else {
1083 
1084                 buffer.append(str);
1085                 // FIX (HJ): Preserve the newline
1086                 buffer.append("\n");
1087             }
1088         }
1089 
1090         StringReader r = new StringReader(buffer.toString());
1091         return r;
1092     }
1093 
1094     /**
1095      *  <p>Transform the InputStream to a Reader Stream.</p>
1096      *
1097      *  <p>This hacked code needs to be changed later on.</p>
1098      *
1099      *  <p>Issue: the new oasis input file stream means
1100      *  that the old input stream fails. see #i33702# </p>
1101      *
1102      *  @param  is  <code>InputStream</code> to be filtered.
1103      *
1104      *  @return  Reader value of the InputStream().
1105      *
1106      *  @throws  IOException  If any I/O error occurs.
1107      */
secondHack(InputStream is)1108     private static Reader secondHack(InputStream is) throws IOException {
1109 
1110         BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
1111         char[] charArray = new char[4096];
1112         StringBuilder sBuf = new StringBuilder();
1113         int n = 0;
1114         while ((n=br.read(charArray, 0, charArray.length)) > 0)
1115             sBuf.append(charArray, 0, n);
1116 
1117         // ensure there is no trailing garbage after the end of the stream.
1118         int sIndex = sBuf.lastIndexOf("</office:document>");
1119         sBuf.delete(sIndex, sBuf.length());
1120         sBuf.append("</office:document>");
1121         StringReader r = new StringReader(sBuf.toString());
1122         return r;
1123     }
1124 
1125 
1126     /**
1127      * Method to create the initial entries in the manifest.xml file stored
1128      * in an SX? file.
1129      */
initManifestDOM()1130     private void initManifestDOM() throws IOException {
1131 
1132         try {
1133             DocumentBuilder builder = factory.newDocumentBuilder();
1134             DOMImplementation domImpl = builder.getDOMImplementation();
1135 
1136             DocumentType docType = domImpl.createDocumentType(TAG_MANIFEST_ROOT,
1137                                         "-//OpenOffice.org//DTD Manifest 1.0//EN",
1138                                         "Manifest.dtd");
1139         manifestDoc = domImpl.createDocument("manifest", TAG_MANIFEST_ROOT, docType);
1140         } catch (ParserConfigurationException ex) {
1141             throw new OfficeDocumentException(ex);
1142         }
1143 
1144         // Add the <manifest:manifest> entry
1145         Element manifestRoot = manifestDoc.getDocumentElement();
1146 
1147         manifestRoot.setAttribute("xmlns:manifest", "http://openoffice.org/2001/manifest");
1148 
1149         Element docRoot = manifestDoc.createElement(TAG_MANIFEST_FILE);
1150 
1151         docRoot.setAttribute(ATTRIBUTE_MANIFEST_FILE_PATH, "/");
1152         docRoot.setAttribute(ATTRIBUTE_MANIFEST_FILE_TYPE, getDocumentMimeType());
1153 
1154         manifestRoot.appendChild(docRoot);
1155     }
1156 }
1157