001    /*
002     * Copyright 2007 - 2007 JEuclid, http://jeuclid.sf.net
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     *      http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    
017    /* $Id: Processor.java,v 5a7becda9147 2009/10/23 10:57:54 max $ */
018    
019    package net.sourceforge.jeuclid.converter;
020    
021    import java.io.IOException;
022    
023    import javax.xml.transform.Result;
024    import javax.xml.transform.Source;
025    import javax.xml.transform.Transformer;
026    import javax.xml.transform.TransformerException;
027    import javax.xml.transform.TransformerFactory;
028    import javax.xml.transform.dom.DOMResult;
029    import javax.xml.transform.dom.DOMSource;
030    
031    import net.sourceforge.jeuclid.LayoutContext;
032    import net.sourceforge.jeuclid.context.LayoutContextImpl;
033    import net.sourceforge.jeuclid.converter.ConverterPlugin.DocumentWithDimension;
034    import net.sourceforge.jeuclid.elements.AbstractJEuclidElement;
035    import net.sourceforge.jeuclid.elements.generic.MathImpl;
036    import net.sourceforge.jeuclid.parser.Parser;
037    
038    import org.apache.commons.logging.Log;
039    import org.apache.commons.logging.LogFactory;
040    import org.w3c.dom.Element;
041    import org.w3c.dom.Node;
042    import org.w3c.dom.NodeList;
043    import org.xml.sax.SAXException;
044    
045    /**
046     * Contains the actual processing routines.
047     * <p>
048     * To use this class obtain an instance of the Processor singleton instance.
049     * Then use the {@link #process(Source, Result)} function to process your
050     * Document.
051     * <p>
052     * This will replace all occurrences of MathML within fo:instream tags by the
053     * equivalent SVG code. It will also add a baseline-shift attribute so that the
054     * formula is in line with the rest of the text.
055     * 
056     * @version $Revision: 5a7becda9147 $
057     */
058    public final class Processor {
059    
060        private static final class SingletonHolder {
061            private static final Processor INSTANCE = new Processor();
062    
063            private SingletonHolder() {
064            }
065        }
066    
067        /**
068         * Logger for this class
069         */
070        private static final Log LOGGER = LogFactory.getLog(Processor.class);
071    
072        // private static final String NAMESPACE_HTML =
073        // "http://www.w3.org/1999/xhtml";
074    
075        private final Transformer transformer;
076    
077        /**
078         * Default constructor.
079         */
080        protected Processor() {
081            Transformer t;
082            try {
083                t = TransformerFactory.newInstance().newTransformer();
084            } catch (final TransformerException e) {
085                t = null;
086                Processor.LOGGER.warn(e.getMessage());
087                assert false;
088            }
089            this.transformer = t;
090        }
091    
092        /**
093         * Retrieve the processor singleton object.
094         * 
095         * @return the Processor.
096         */
097        public static Processor getInstance() {
098            return Processor.SingletonHolder.INSTANCE;
099        }
100    
101        /**
102         * use {@link #getInstance()} instead.
103         * 
104         * @return see {@link #getInstance()}
105         * @throws TransformerException
106         *             see {@link #getInstance()}
107         * @deprecated use {@link #getInstance()} instead.
108         */
109        @Deprecated
110        public static Processor getProcessor() throws TransformerException {
111            return Processor.getInstance();
112        }
113    
114        /**
115         * Pre-process a .fo file.
116         * 
117         * @param inputSource
118         *            Input File
119         * @param result
120         *            Output File
121         * @param context
122         *            LayoutContext.
123         * @throws TransformerException
124         *             an error occurred during the processing.
125         */
126        public void process(final Source inputSource, final Result result,
127                final LayoutContext context) throws TransformerException {
128            Processor.LOGGER.info("Processing " + inputSource.getSystemId()
129                    + " to " + result.getSystemId());
130            try {
131                final Node doc = Parser.getInstance().parse(inputSource);
132                this.processSubtree(doc, context);
133                final DOMSource source = new DOMSource(doc);
134                this.transformer.transform(source, result);
135            } catch (final IOException e) {
136                throw new TransformerException("IOException", e);
137            } catch (final SAXException e) {
138                throw new TransformerException("SAXException", e);
139            }
140        }
141    
142        /**
143         * Pre-process a .fo file.
144         * 
145         * @param inputSource
146         *            Input File
147         * @param result
148         *            Output File
149         * @throws TransformerException
150         *             an error occurred during the processing.
151         */
152        public void process(final Source inputSource, final Result result)
153                throws TransformerException {
154            this.process(inputSource, result, LayoutContextImpl
155                    .getDefaultLayoutContext());
156        }
157    
158        private void processSubtree(final Node node, final LayoutContext context) {
159            if (AbstractJEuclidElement.URI.equals(node.getNamespaceURI())
160                    && MathImpl.ELEMENT.equals(node.getLocalName())) {
161    
162                final DocumentWithDimension svgdocdim = Converter
163                        .getInstance()
164                        .convert(
165                                node,
166                                net.sourceforge.jeuclid.converter.Converter.TYPE_SVG,
167                                context);
168    
169                final float baselinePercent = -(svgdocdim.getBaseline() / (float) svgdocdim
170                        .getDimension().getHeight()) * 100f;
171    
172                final Node parent = node.getParentNode();
173                if ("http://www.w3.org/1999/XSL/Format".equals(parent
174                        .getNamespaceURI())
175                        && "instream-foreign-object".equals(parent.getLocalName())) {
176                    final Element pElement = (Element) parent;
177                    pElement
178                            .setAttribute("alignment-adjust", baselinePercent + "%");
179                }
180                this.safeReplaceChild(parent, node, svgdocdim.getDocument()
181                        .getFirstChild());
182            } else {
183                this.processChildren(node, context);
184                // TODO: This is an IE-Fix, but does not work yet.
185                // final Node parent = node.getParentNode();
186                // if ((parent != null)
187                // && (Processor.NAMESPACE_HTML.equals(parent
188                // .getNamespaceURI()))
189                // && ("html".equals(parent.getLocalName()))
190                // && ("head".equals(node.getLocalName()))) {
191                // ((Element) parent).setAttribute("xmlns:svg",
192                // "http://www.w3.org/2000/svg");
193                // final Document ownerDoc = node.getOwnerDocument();
194                // final Element objectElement = ownerDoc
195                // .createElement("object");
196                // objectElement.setAttribute("id", "AdobeSVG");
197                // objectElement.setAttribute("classid",
198                // "clsid:78156a80-c6a1-4bbf-8e6a-3cd390eeb4e2");
199                // node.appendChild(objectElement);
200                // final ProcessingInstruction pi = ownerDoc
201                // .createProcessingInstruction("import",
202                // "namespace=\"svg\" implementation=\"#AdobeSVG\"");
203                // node.appendChild(pi);
204                // }
205            }
206        }
207    
208        private void safeReplaceChild(final Node parent, final Node oldChild,
209                final Node newChild) {
210            try {
211                final DOMSource source = new DOMSource(newChild);
212                final DOMResult result = new DOMResult(parent);
213    
214                this.transformer.transform(source, result);
215            } catch (final TransformerException e) {
216                Processor.LOGGER.warn("TranformerException: " + e.getMessage());
217            }
218            parent.removeChild(oldChild);
219        }
220    
221        private void processChildren(final Node node, final LayoutContext context) {
222            final NodeList childList = node.getChildNodes();
223            if (childList != null) {
224                for (int i = 0; i < childList.getLength(); i++) {
225                    final Node child = childList.item(i);
226                    this.processSubtree(child, context);
227                }
228            }
229        }
230    
231    }