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 }