001    /*
002     * Copyright 2002 - 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: DOMBuilder.java,v 2bab6eb875e8 2010/08/11 16:45:50 max $ */
018    
019    package net.sourceforge.jeuclid;
020    
021    import javax.annotation.concurrent.GuardedBy;
022    import javax.annotation.concurrent.ThreadSafe;
023    import javax.xml.transform.Transformer;
024    import javax.xml.transform.TransformerException;
025    import javax.xml.transform.TransformerFactory;
026    import javax.xml.transform.dom.DOMResult;
027    import javax.xml.transform.dom.DOMSource;
028    import javax.xml.transform.stream.StreamSource;
029    
030    import net.sourceforge.jeuclid.elements.generic.DocumentElement;
031    
032    import org.apache.commons.logging.Log;
033    import org.apache.commons.logging.LogFactory;
034    import org.w3c.dom.DOMException;
035    import org.w3c.dom.Document;
036    import org.w3c.dom.DocumentFragment;
037    import org.w3c.dom.Element;
038    import org.w3c.dom.Node;
039    
040    /**
041     * Builds a MathML tree from a given DOM tree.
042     * 
043     * @version $Revision: 2bab6eb875e8 $
044     */
045    @ThreadSafe
046    public final class DOMBuilder {
047        /**
048         * Logger for this class
049         */
050        private static final Log LOGGER = LogFactory.getLog(DOMBuilder.class);
051    
052        private static final class SingletonHolder {
053            private static final DOMBuilder INSTANCE = new DOMBuilder();
054    
055            private SingletonHolder() {
056            }
057        }
058    
059        @GuardedBy("itself")
060        private final Transformer contentTransformer;
061    
062        @GuardedBy("itself")
063        private final Transformer identityTransformer;
064    
065        @GuardedBy("itself")
066        private final Transformer namespaceTransformer;
067    
068        /**
069         * Default constructor.
070         */
071        protected DOMBuilder() {
072            this.identityTransformer = this.createIdentityTransformer();
073            this.contentTransformer = this.createTransformer(
074                    "/net/sourceforge/jeuclid/content/mathmlc2p.xsl",
075                    this.identityTransformer);
076            this.namespaceTransformer = this.createTransformer(
077                    "/net/sourceforge/jeuclid/addMathMLNamespace.xsl",
078                    this.identityTransformer);
079        }
080    
081        private Transformer createIdentityTransformer() {
082            Transformer t;
083            try {
084                t = TransformerFactory.newInstance().newTransformer();
085            } catch (final TransformerException e) {
086                DOMBuilder.LOGGER.warn(e.getMessage());
087                t = null;
088                assert false;
089            }
090            return t;
091        }
092    
093        private Transformer createTransformer(final String sourceFile,
094                final Transformer fallback) {
095            Transformer t;
096            try {
097                t = TransformerFactory.newInstance().newTemplates(
098                        new StreamSource(DOMBuilder.class
099                                .getResourceAsStream(sourceFile))).newTransformer();
100            } catch (final TransformerException e) {
101                DOMBuilder.LOGGER.warn(e.getMessage());
102                t = fallback;
103            }
104            return t;
105        }
106    
107        /**
108         * @return the singleton instance of the DOMBuilder
109         */
110        public static DOMBuilder getInstance() {
111            return DOMBuilder.SingletonHolder.INSTANCE;
112        }
113    
114        /**
115         * use {@link #getInstance()} instead.
116         * 
117         * @return see {@link #getInstance()}
118         * @deprecated use {@link #getInstance()} instead.
119         */
120        @Deprecated
121        public static DOMBuilder getDOMBuilder() {
122            return DOMBuilder.getInstance();
123        }
124    
125        /**
126         * Constructs a builder with content math support.
127         * 
128         * @param node
129         *            The MathML document. Can be an instance of Document, Element
130         *            or DocumentFragment with Element child
131         * @return the parsed Document
132         * @see #createJeuclidDom(Node, boolean)
133         */
134        public DocumentElement createJeuclidDom(final Node node) {
135            return this.createJeuclidDom(node, true);
136        }
137    
138        /**
139         * Constructs a builder.
140         * <p>
141         * This constructor needs a valid DOM Tree. To obtain a DOM tree, you may
142         * use {@link MathMLParserSupport}.
143         * 
144         * @param node
145         *            The MathML document. Can be an instance of Document, Element
146         *            or DocumentFragment with Element child
147         * @param supportContent
148         *            if set to true, content Math will be supported. This impacts
149         *            performance.
150         * @return the parsed Document
151         * @see MathMLParserSupport
152         */
153        public DocumentElement createJeuclidDom(final Node node,
154                final boolean supportContent) {
155            return this.createJeuclidDom(node, supportContent, false);
156        }
157    
158        /**
159         * Constructs a builder.
160         * <p>
161         * This constructor needs a valid DOM Tree. To obtain a DOM tree, you may
162         * use {@link MathMLParserSupport}.
163         * 
164         * @param node
165         *            The MathML document. Can be an instance of Document, Element
166         *            or DocumentFragment with Element child
167         * @param supportContent
168         *            if set to true, content Math will be supported. This impacts
169         *            performance.
170         * @param addNamespace
171         *            if set to true, the MathML namespace will be added to all
172         *            elements.
173         * @return the parsed Document
174         * @see MathMLParserSupport
175         */
176        public DocumentElement createJeuclidDom(final Node node,
177                final boolean supportContent, final boolean addNamespace) {
178            Node documentElement;
179            if (node instanceof Document) {
180                documentElement = ((Document) node).getDocumentElement();
181            } else if (node instanceof Element) {
182                documentElement = node;
183            } else if (node instanceof DocumentFragment) {
184                final Node child = node.getFirstChild();
185                if (!(child instanceof Element)) {
186                    throw new IllegalArgumentException(
187                            "Expected DocumentFragment with Element child");
188                }
189                documentElement = child;
190            } else {
191                throw new IllegalArgumentException("Unsupported node: " + node
192                        + ". Expected either Document, Element or DocumentFragment");
193            }
194    
195            if (addNamespace) {
196                documentElement = this.applyTransform(documentElement,
197                        this.namespaceTransformer);
198            }
199    
200            final DocumentElement d;
201            if (supportContent) {
202                d = this.applyTransform(documentElement, this.contentTransformer);
203            } else {
204                d = this.applyTransform(documentElement, this.identityTransformer);
205            }
206            return d;
207        }
208    
209        private DocumentElement applyTransform(final Node src,
210                final Transformer transformer) {
211            DocumentElement d;
212            try {
213                final DOMSource source = new DOMSource(src);
214                d = new DocumentElement();
215                final DOMResult result = new DOMResult(d);
216                synchronized (transformer) {
217                    transformer.transform(source, result);
218                }
219            } catch (final TransformerException e) {
220                d = null;
221                DOMBuilder.LOGGER.warn(e.getMessage());
222            } catch (final NullPointerException e) {
223                d = null;
224                // Happens if the stylesheet was not loaded correctly
225                DOMBuilder.LOGGER.warn(e.getMessage());
226            } catch (final DOMException e) {
227                d = null;
228                DOMBuilder.LOGGER.warn(e.getMessage());
229            }
230            return d;
231        }
232    
233    }