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 <!DOCTYPE> 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 <!DOCTYPE> 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 <!DOCTYPE> 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