root/trunk/docx4j/src/main/java/org/docx4j/openpackaging/packages/WordprocessingMLPackage.java

Revision 570, 17.6 KB (checked in by jharrop, 4 weeks ago)

Tested use of org.docx4j.convert.*.XmlPackage? in transforms method (used in filter)

  • Property svn:eol-style set to native
  • Property svn:executable set to *
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.packages;
22
23
24import java.io.File;
25import java.io.FileInputStream;
26import java.io.OutputStream;
27import java.util.Iterator;
28import java.util.Map;
29
30import javax.xml.bind.JAXBContext;
31import javax.xml.bind.JAXBElement;
32import javax.xml.bind.Marshaller;
33import javax.xml.bind.Unmarshaller;
34import javax.xml.parsers.DocumentBuilderFactory;
35
36import org.apache.log4j.Logger;
37import org.docx4j.convert.out.xmlPackage.XmlPackage;
38import org.docx4j.fonts.Substituter;
39import org.docx4j.fonts.FontUtils;
40import org.docx4j.jaxb.Context;
41import org.docx4j.openpackaging.contenttype.ContentType;
42import org.docx4j.openpackaging.contenttype.ContentTypeManager;
43import org.docx4j.openpackaging.contenttype.ContentTypeManagerImpl;
44import org.docx4j.openpackaging.contenttype.ContentTypes;
45import org.docx4j.openpackaging.exceptions.Docx4JException;
46import org.docx4j.openpackaging.exceptions.InvalidFormatException;
47import org.docx4j.openpackaging.io.LoadFromZipFile;
48import org.docx4j.openpackaging.io.SaveToZipFile;
49import org.docx4j.openpackaging.parts.DocPropsCorePart;
50import org.docx4j.openpackaging.parts.DocPropsCustomPart;
51import org.docx4j.openpackaging.parts.DocPropsExtendedPart;
52import org.docx4j.openpackaging.parts.JaxbXmlPart;
53import org.docx4j.openpackaging.parts.Part;
54import org.docx4j.openpackaging.parts.WordprocessingML.FontTablePart;
55import org.docx4j.openpackaging.parts.WordprocessingML.GlossaryDocumentPart;
56import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart;
57import org.docx4j.openpackaging.parts.WordprocessingML.StyleDefinitionsPart;
58import org.docx4j.openpackaging.parts.relationships.Namespaces;
59
60import com.lowagie.text.pdf.BaseFont;
61
62
63
64
65
66
67
68/**
69 * @author jharrop
70 *
71 */
72public class WordprocessingMLPackage extends Package {
73       
74        // What is a Word document these days?
75        //
76        // Well, a package is a logical entity which holds a collection of parts       
77        // And a word document is exactly a WordProcessingML package   
78        // Which has a Main Document Part, and optionally, a Glossary Document Part
79
80        /* So its a Word doc if:
81         * 1. _rels/.rels tells you where to find an office document
82         * 2. [Content_Types].xml tells you that office document is   
83         *    of content type application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml
84       
85         * A minimal docx has:
86         *
87         * [Content_Types].xml containing:
88         * 1. <Default Extension="rels" ...
89         * 2. <Override PartName="/word/document.xml"...
90         *
91         * _rels/.rels with a target for word/document.xml
92         *
93         * word/document.xml
94         */
95       
96        protected static Logger log = Logger.getLogger(WordprocessingMLPackage.class);
97               
98       
99        // Main document
100        protected MainDocumentPart mainDoc;
101       
102        // (optional) Glossary document
103        protected GlossaryDocumentPart glossaryDoc;
104       
105        /**
106         * Constructor.  Also creates a new content type manager
107         *
108         */     
109        public WordprocessingMLPackage() {
110                super();
111                setContentType(new ContentType(ContentTypes.WORDPROCESSINGML_DOCUMENT));
112        }
113        /**
114         * Constructor.
115         * 
116         * @param contentTypeManager
117         *            The content type manager to use
118         */
119        public WordprocessingMLPackage(ContentTypeManager contentTypeManager) {
120                super(contentTypeManager);
121                setContentType(new ContentType(ContentTypes.WORDPROCESSINGML_DOCUMENT));
122        }
123       
124        /**
125         * Convenience method to create a WordprocessingMLPackage
126         * from an existing File.
127     *
128         * @param docxFile
129         *            The docx file
130         */     
131        public static WordprocessingMLPackage load(java.io.File docxFile) throws Docx4JException {
132               
133                LoadFromZipFile loader = new LoadFromZipFile();
134                return (WordprocessingMLPackage)loader.get(docxFile);           
135        }
136
137        /**
138         * Convenience method to save a WordprocessingMLPackage
139         * to a File.
140     *
141         * @param docxFile
142         *            The docx file
143         */     
144        public void save(java.io.File docxFile) throws Docx4JException {
145               
146                SaveToZipFile saver = new SaveToZipFile(this);
147                saver.save(docxFile);
148        }
149       
150       
151        public boolean setPartShortcut(Part part, String relationshipType) {
152                if (relationshipType.equals(Namespaces.PROPERTIES_CORE)) {
153                        docPropsCorePart = (DocPropsCorePart)part;
154                        log.info("Set shortcut for docPropsCorePart");
155                        return true;                   
156                } else if (relationshipType.equals(Namespaces.PROPERTIES_EXTENDED)) {
157                        docPropsExtendedPart = (DocPropsExtendedPart)part;
158                        log.info("Set shortcut for docPropsExtendedPart");
159                        return true;                   
160                } else if (relationshipType.equals(Namespaces.PROPERTIES_CUSTOM)) {
161                        docPropsCustomPart = (DocPropsCustomPart)part;
162                        log.info("Set shortcut for docPropsCustomPart");
163                        return true;                   
164                } else if (relationshipType.equals(Namespaces.DOCUMENT)) {
165                        mainDoc = (MainDocumentPart)part;
166                        log.info("Set shortcut for mainDoc");
167                        return true;                   
168                } else {       
169                        return false;
170                }
171        }
172       
173        public MainDocumentPart getMainDocumentPart() {
174                return mainDoc;
175        }
176       
177   
178    /**
179     * Use an XSLT to alter the contents of this package.
180     * The output of the transformation must be valid
181     * pck:package/pck:part format, as emitted by Word 2007.
182     *
183     * @param xslt
184     * @param transformParameters
185     * @throws Exception
186     */   
187    public void transform(java.io.InputStream xslt,
188                          Map<String, Object> transformParameters) throws Exception {
189
190        // Prepare in the input document
191       
192                XmlPackage worker = new XmlPackage(this);
193                org.docx4j.xmlPackage.Package pkg = worker.get();
194       
195                JAXBContext jc = Context.jcXmlPackage;
196                Marshaller marshaller=jc.createMarshaller();
197                org.w3c.dom.Document doc = org.docx4j.XmlUtils.neww3cDomDocument();
198                marshaller.marshal(pkg, doc);
199       
200                javax.xml.bind.util.JAXBResult result = new javax.xml.bind.util.JAXBResult(jc );
201               
202                // Perform the transformation
203                org.docx4j.XmlUtils.transform(doc, xslt, transformParameters, result);
204               
205
206                //org.docx4j.xmlPackage.Package wmlPackageEl = (org.docx4j.xmlPackage.Package)result.getResult();
207                javax.xml.bind.JAXBElement je = (javax.xml.bind.JAXBElement)result.getResult();
208                org.docx4j.xmlPackage.Package wmlPackageEl = (org.docx4j.xmlPackage.Package)je.getValue();
209                org.docx4j.convert.in.XmlPackage xmlPackage = new org.docx4j.convert.in.XmlPackage( wmlPackageEl);
210               
211                ContentTypeManager ctm = new ContentTypeManagerImpl();
212               
213                Part tmpDocPart = xmlPackage.getRawPart(ctm,  "/word/document.xml");
214                Part tmpStylesPart = xmlPackage.getRawPart(ctm,  "/word/styles.xml");
215               
216                // This code assumes all the existing rels etc of
217                // the existing main document part are still relevant.
218//              if (wmlDocument==null) {
219//                      log.warn("Couldn't get main document part from package transform result!");                     
220//              } else {
221//                      this.getMainDocumentPart().setJaxbElement(wmlDocument);
222//              }       
223                this.getMainDocumentPart().setJaxbElement(  ((JaxbXmlPart)tmpDocPart).getJaxbElement() );
224//                             
225//              if (wmlStyles==null) {
226//                      log.warn("Couldn't get style definitions part from package transform result!");                 
227//              } else {
228//                      this.getMainDocumentPart().getStyleDefinitionsPart().setJaxbElement(wmlStyles);
229//              }
230                this.getMainDocumentPart().getStyleDefinitionsPart().setJaxbElement(  ((JaxbXmlPart)tmpStylesPart).getJaxbElement() );
231       
232    }
233   
234    public void filter( FilterSettings filterSettings ) throws Exception {
235
236                java.io.InputStream xslt
237                        = org.docx4j.utils.ResourceUtils.getResource(
238                                        "org/docx4j/openpackaging/packages/filter.xslt");
239       
240        transform(xslt, filterSettings.getSettings() );
241       
242    }
243
244   
245   
246   
247    public void setFontSubstituter(Substituter fs) throws Exception {
248        if (fs == null) {
249                throw new IllegalArgumentException("Font Substituter cannot be null.");
250        }
251                // 1.  Get a list of all the fonts in the document
252                java.util.Map fontsInUse = this.getMainDocumentPart().fontsInUse();
253               
254                // 2.  For each font, find the closest match on the system (use OO's VCL.xcu to do this)
255                //     - do this in a general way, since docx4all needs this as well to display fonts           
256                fontSubstituter = fs;
257                org.docx4j.wml.Fonts fonts = null;
258                FontTablePart fontTablePart= this.getMainDocumentPart().getFontTablePart();     
259               
260                if (fontTablePart==null) {
261                        log.warn("FontTable missing; creating default part.");
262                        fontTablePart= new org.docx4j.openpackaging.parts.WordprocessingML.FontTablePart();
263                        fontTablePart.unmarshalDefaultFonts();
264                        fontTablePart.processEmbeddings();
265                }
266               
267                fonts = (org.docx4j.wml.Fonts)fontTablePart.getJaxbElement();
268                fontSubstituter.populateFontMappings(fontsInUse, fonts);       
269       
270    }
271
272    public Substituter getFontSubstituter() {
273                return fontSubstituter;
274        }
275
276        private Substituter fontSubstituter;
277   
278        /** Create a pdf version of the document.
279         *
280         * @param os
281         *            The OutputStream to write the pdf to
282         *
283         * */     
284    public void pdf(OutputStream os) throws Exception {
285       
286        /*
287         * There are 2 broad approaches we could use to render the document
288         * as a PDF:
289         *
290         * 1.  XSL-FO
291         * 2.  XHTML to PDF
292         *
293         * Given that a word2html.xsl is already freely available, we use
294         * the second approach.
295         *
296         * The question then is how the stylesheet is made to work with
297         * our main document and style definition parts.
298         *
299         * it processes pck:package/pck:part
300         *
301         */
302                               
303        // Put the html in result
304                org.w3c.dom.Document xhtmlDoc = org.docx4j.XmlUtils.neww3cDomDocument();
305                javax.xml.transform.dom.DOMResult result = new javax.xml.transform.dom.DOMResult(xhtmlDoc);
306                org.docx4j.convert.out.html.HtmlExporter.html(this, result, false,
307                                System.getProperty("java.io.tmpdir") ); // false -> don't use HTML fonts.
308                               
309                // Now render the XHTML
310                org.xhtmlrenderer.pdf.ITextRenderer renderer = new org.xhtmlrenderer.pdf.ITextRenderer();
311                               
312                // 4.  Use addFont code like that below as necessary for the fonts
313               
314                        // See https://xhtmlrenderer.dev.java.net/r7/users-guide-r7.html#xil_32
315                org.xhtmlrenderer.extend.FontResolver resolver = renderer.getFontResolver();           
316                               
317                Map fontMappings = getFontSubstituter().getFontMappings();
318                Map fontsInUse = this.getMainDocumentPart().fontsInUse();
319                Iterator fontMappingsIterator = fontsInUse.entrySet().iterator();
320                while (fontMappingsIterator.hasNext()) {
321                Map.Entry pairs = (Map.Entry)fontMappingsIterator.next();
322                if(pairs.getKey()==null) {
323                        log.info("Skipped null key");
324                        pairs = (Map.Entry)fontMappingsIterator.next();
325                }
326               
327                String fontName = (String)pairs.getKey();
328                embed(renderer, Substituter.normalise(fontName), fontMappings);         
329                // For any font we embed, also embed the bold, italic, and bold italic substitute
330                // .. at present, we can't tell which of these forms are actually used, so add them all
331                embed(renderer, Substituter.normalise(fontName + Substituter.BOLD), fontMappings);
332                embed(renderer, Substituter.normalise(fontName + Substituter.ITALIC), fontMappings);
333                embed(renderer, Substituter.normalise(fontName + Substituter.BOLD_ITALIC), fontMappings);
334               
335            }
336               
337            // TESTING
338//          xhtmlDoc = org.docx4j.XmlUtils.neww3cDomDocument();
339//          try {
340//                      javax.xml.parsers.DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
341//                      dbf.setNamespaceAware(true);
342//                      dbf.newDocumentBuilder().newDocument();
343//             
344//                      xhtmlDoc = dbf.newDocumentBuilder().parse(new File("C:\\Users\\jharrop\\workspace\\docx4all\\sample-docs\\comic.html"));
345//        } catch (Exception e) {
346//            e.printStackTrace();
347//        }         
348           
349                renderer.setDocument(xhtmlDoc, null);
350                renderer.layout();
351               
352                renderer.createPDF(os);
353               
354        }
355        /**
356         * @param renderer
357         * @param fontName
358         * @param fm
359         */
360        private void embed(org.xhtmlrenderer.pdf.ITextRenderer renderer,
361                        String fontName, Map fontMappings) {
362                Substituter.FontMapping fm = (Substituter.FontMapping)fontMappings.get( fontName );
363               
364                if (fm == null) {
365                        log.warn("No mapping found for: " + fontName);
366                } else if (fm.getPhysicalFont()!=null) {
367                        try {
368                                if (fm.getPhysicalFont().getEmbeddedFile().endsWith(".pfb")) {
369                                       
370//                                              String afm = fm.getPhysicalFont().getEmbeddedFile().substring(5, fm.getPhysicalFont().getEmbeddedFile().length()-4 ) + ".afm";  // drop the 'file:'
371                                        String afm = FontUtils.pathFromURL(fm.getPhysicalFont().getEmbeddedFile());
372                                        afm = afm.substring(0, afm.length()-4 ) + ".afm";  // drop the 'file:'
373                                        log.info("Looking for: " + afm);
374                                       
375                                        // Given the check in substituter, we expect to find one or the other.
376                                        File f = new File(afm);
377                                if (f.exists()) {                               
378                                        log.info("Got it");
379                                        renderer.getFontResolver().addFont(afm, BaseFont.CP1252, true, FontUtils.pathFromURL(fm.getPhysicalFont().getEmbeddedFile()));  // drop the 'file:'     
380                                                log.info("Substituting " + fontName + " with embedding " + fm.getPhysicalFont().getFamilyName() + " from " + fm.getPhysicalFont().getEmbeddedFile() );
381                                } else {
382                                        // Should we be doing afm first, or pfm?
383                                                String pfm = FontUtils.pathFromURL(fm.getPhysicalFont().getEmbeddedFile());
384                                                pfm = pfm.substring(0, pfm.length()-4 ) + ".pfm";  // drop the 'file:'
385                                                log.info("Looking for: " + pfm);
386                                                f = new File(pfm);
387                                        if (f.exists()) {                               
388                                                log.info("Got it");
389                                                renderer.getFontResolver().addFont(pfm, BaseFont.CP1252, true, FontUtils.pathFromURL(fm.getPhysicalFont().getEmbeddedFile() ));  // drop the 'file:'
390                                                        log.info("Substituting " + fontName + " with embedding " + fm.getPhysicalFont().getFamilyName() + " from " + fm.getPhysicalFont().getEmbeddedFile() );
391                                        } else {
392                                                // Shouldn't happen.
393                                                log.error("Couldn't find afm or pfm corresponding to " + fm.getPhysicalFont().getEmbeddedFile());
394                                        }
395                                }
396                                } else {                               
397                                        renderer.getFontResolver().addFont(FontUtils.pathFromURL(fm.getPhysicalFont().getEmbeddedFile()), true);
398                                        log.info("Substituting " + fontName + " with embedding " + fm.getPhysicalFont().getFamilyName() + " from " + fm.getPhysicalFont().getEmbeddedFile() );
399                                }
400                        } catch (java.io.IOException e) {
401                       
402                        /*
403                         * [AWT-EventQueue-0] INFO  packages.WordprocessingMLPackage - Substituting symbol with standardsymbolsl from file:/usr/share/fonts/type1/gsfonts/s050000l.pfb
404java.io.IOException: Unsupported font type
405at org.xhtmlrenderer.pdf.ITextFontResolver.addFont(ITextFontResolver.java:199)
406
407.pfb not supported, even with iText 2.0.8
408
409                         */
410                                e.printStackTrace();
411                                log.warn("Shouldn't happen - should have been detected upstream ... " +  e.getMessage() + ": " + fm.getPhysicalFont().getEmbeddedFile());
412                        } catch (Exception e) {
413                                e.printStackTrace();
414                                log.error("Shouldn't happen - should have been detected upstream ... " + e.getMessage());
415                        }
416                } else {
417                        log.warn("Can't addFont for: " + fontName);
418                }
419        }       
420       
421
422        public static WordprocessingMLPackage createPackage() throws InvalidFormatException {
423               
424                               
425                // Create a package
426                WordprocessingMLPackage wmlPack = new WordprocessingMLPackage();
427
428                // Create main document part
429                Part wordDocumentPart = new org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart();         
430               
431                // Create main document part content
432                org.docx4j.wml.ObjectFactory factory = new org.docx4j.wml.ObjectFactory();
433               
434                org.docx4j.wml.Body  body = factory.createBody();
435               
436                org.docx4j.wml.Document wmlDocumentEl = factory.createDocument();
437                wmlDocumentEl.setBody(body);
438                               
439                // Put the content in the part
440                ((org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart)wordDocumentPart).setJaxbElement(wmlDocumentEl);
441                                               
442                // Add the main document part to the package relationships
443                // (creating it if necessary)
444                wmlPack.addTargetPart(wordDocumentPart);
445                               
446                // Create a styles part
447                Part stylesPart = new org.docx4j.openpackaging.parts.WordprocessingML.StyleDefinitionsPart();
448                try {
449                        ((org.docx4j.openpackaging.parts.WordprocessingML.StyleDefinitionsPart) stylesPart)
450                                        .unmarshalDefaultStyles();
451                       
452                        // Add the styles part to the main document part relationships
453                        // (creating it if necessary)
454                        wordDocumentPart.addTargetPart(stylesPart); // NB - add it to main doc part, not package!                       
455                       
456                } catch (Exception e) {
457                        // TODO: handle exception
458                        e.printStackTrace();                   
459                }
460                // Return the new package
461                return wmlPack;
462               
463        }
464       
465        public static class FilterSettings {
466               
467                Boolean removeProofErrors = Boolean.FALSE;             
468                public void setRemoveProofErrors(boolean val) {
469                        removeProofErrors = new Boolean(val);
470                }
471
472                Boolean removeContentControls = Boolean.FALSE;         
473                public void setRemoveContentControls(boolean val) {
474                        removeContentControls = new Boolean(val);
475                }
476               
477                Boolean removeRsids = Boolean.FALSE;           
478                public void setRemoveRsids(boolean val) {
479                        removeRsids = new Boolean(val);
480                }
481               
482                Boolean tidyForDocx4all = Boolean.FALSE;               
483                public void setTidyForDocx4all(boolean val) {
484                        tidyForDocx4all = new Boolean(val);
485                }
486               
487               
488                Map<String, Object> getSettings() {
489                        Map<String, Object> settings = new java.util.HashMap<String, Object>();
490                       
491                        settings.put("removeProofErrors", removeProofErrors);
492                        settings.put("removeContentControls", removeContentControls);
493                        settings.put("removeRsids", removeRsids);
494                        settings.put("tidyForDocx4all", tidyForDocx4all);
495                       
496                        return settings;
497                }
498               
499               
500        }
501
502       
503}
Note: See TracBrowser for help on using the browser.