001 /*
002 * Copyright 2009 - 2009 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: OperatorDictionary3.java,v f5d68b2c52ae 2010/08/13 10:03:30 max $ */
018
019 package net.sourceforge.jeuclid.elements.support.operatordict;
020
021 import java.io.IOException;
022 import java.io.InputStream;
023 import java.io.Serializable;
024 import java.util.EnumMap;
025 import java.util.HashMap;
026 import java.util.Iterator;
027 import java.util.Map;
028
029 import javax.xml.XMLConstants;
030 import javax.xml.namespace.NamespaceContext;
031 import javax.xml.xpath.XPath;
032 import javax.xml.xpath.XPathConstants;
033 import javax.xml.xpath.XPathExpression;
034 import javax.xml.xpath.XPathExpressionException;
035 import javax.xml.xpath.XPathFactory;
036
037 import net.sourceforge.jeuclid.Constants;
038 import net.sourceforge.jeuclid.elements.support.NamespaceContextAdder;
039 import net.sourceforge.jeuclid.parser.Parser;
040
041 import org.apache.commons.logging.Log;
042 import org.apache.commons.logging.LogFactory;
043 import org.w3c.dom.Document;
044 import org.w3c.dom.Node;
045 import org.w3c.dom.NodeList;
046 import org.xml.sax.SAXException;
047
048 /**
049 * Implements an operator dictionary based on the MathML 3 spec.
050 *
051 * @version $Revision: f5d68b2c52ae $
052 */
053 public final class OperatorDictionary3 extends AbstractOperatorDictionary
054 implements Serializable {
055
056 /**
057 * Logger for this class.
058 */
059 private static final Log LOGGER = LogFactory
060 .getLog(OperatorDictionary3.class);
061
062 private static final String NO_SPACE = "0em";
063
064 private static final int INT_NO_SPACE = 0;
065
066 private static final int INT_VERYVERYTHINMATHSPACE = 1;
067
068 private static final int INT_VERYTHINMATHSPACE = 2;
069
070 private static final int INT_THINMATHSPACE = 3;
071
072 private static final int INT_MEDIUMMATHSPACE = 4;
073
074 private static final int INT_THICKMATHSPACE = 5;
075
076 private static final int INT_VERYTHICKMATHSPACE = 6;
077
078 private static final int INT_VERYVERYTHICKMATHSPACE = 7;
079
080 /**
081 *
082 */
083 private static final long serialVersionUID = 1L;
084
085 /**
086 * MathML dictionary resource.
087 */
088 private static final String DICTIONARY_FILE = "/net/sourceforge/jeuclid/appendixc.xml";
089
090 /**
091 * MathML dictionary serialized resource.
092 */
093 private static final String DICTIONARY_SERIALIZED = "/net/sourceforge/jeuclid/appendixc.ser";
094
095 /**
096 * The instance of the Dictionary
097 */
098 private static OperatorDictionary instance;
099
100 private class PersonalNamespaceContext implements NamespaceContext {
101
102 public PersonalNamespaceContext() {
103 }
104
105 /** {@inheritDoc} */
106 public String getNamespaceURI(final String prefix) {
107 final String retVal;
108 if ("html".equals(prefix)) {
109 retVal = "http://www.w3.org/1999/xhtml";
110 } else if ("xml".equals(prefix)) {
111 retVal = XMLConstants.XML_NS_URI;
112 } else {
113 retVal = XMLConstants.NULL_NS_URI;
114 }
115 return retVal;
116 }
117
118 /** {@inheritDoc} */
119 // This method isn't necessary for XPath processing.
120 public String getPrefix(final String uri) {
121 throw new UnsupportedOperationException();
122 }
123
124 /** {@inheritDoc} */
125 // This method isn't necessary for XPath processing either.
126 public Iterator<String> getPrefixes(final String uri) {
127 throw new UnsupportedOperationException();
128 }
129
130 }
131
132 private OperatorDictionary3() {
133 // nothing to do.
134 }
135
136 /**
137 * Get the for singleton instance.
138 *
139 * @return an instance of OperatorDictionary.
140 */
141 public static OperatorDictionary getInstance() {
142 synchronized (OperatorDictionary3.class) {
143 if (OperatorDictionary3.instance == null) {
144 final OperatorDictionary newDict = AbstractOperatorDictionary
145 .deserialize(OperatorDictionary3.DICTIONARY_SERIALIZED);
146 if (newDict == null) {
147 OperatorDictionary3.instance = new OperatorDictionary3();
148 } else {
149 OperatorDictionary3.instance = newDict;
150 }
151 }
152 }
153 return OperatorDictionary3.instance;
154 }
155
156 /** {@inheritDoc} */
157 @Override
158 protected void initializeFromXML(
159 final Map<OperatorAttribute, Map<String, Map<OperatorForm, String>>> dict) {
160 try {
161 final Document doc = this.loadDocument();
162 final XPath xpath = this.createXPath();
163 this.extractValuesFromDocument(doc, xpath, dict);
164 } catch (final SAXException e) {
165 OperatorDictionary3.LOGGER.warn(
166 "XML Could not be parsed in operator dictionary", e);
167 } catch (final IOException e) {
168 OperatorDictionary3.LOGGER.warn(
169 "IO error reading operator dictionary", e);
170 } catch (final XPathExpressionException e) {
171 OperatorDictionary3.LOGGER.warn(
172 "XPath error in operator dictionary", e);
173 }
174 }
175
176 /**
177 * @param doc
178 * @param xpath
179 * @throws XPathExpressionException
180 */
181 private void extractValuesFromDocument(
182 final Document doc,
183 final XPath xpath,
184 final Map<OperatorAttribute, Map<String, Map<OperatorForm, String>>> dict)
185 throws XPathExpressionException {
186 final XPathExpression expr = xpath
187 .compile("//html:table[@class='sortable']/html:tbody/html:tr");
188
189 final XPathExpression operatorExpr = xpath
190 .compile("html:th[2]/text()");
191 final XPathExpression formExpr = xpath.compile("html:th[4]/text()");
192 final XPathExpression lspaceExpr = xpath.compile("html:td[2]/text()");
193 final XPathExpression rspaceExpr = xpath.compile("html:td[3]/text()");
194 final XPathExpression minsizeExpr = xpath
195 .compile("html:td[4]/text()");
196 final XPathExpression propertiesExpr = xpath
197 .compile("html:td[5]/text()");
198
199 final NodeList result = (NodeList) expr.evaluate(doc,
200 XPathConstants.NODESET);
201
202 for (int i = 0; i < result.getLength(); i++) {
203 final Node trNode = result.item(i);
204 final String operator = (String) operatorExpr.evaluate(trNode,
205 XPathConstants.STRING);
206 final String formStr = (String) formExpr.evaluate(trNode,
207 XPathConstants.STRING);
208 final OperatorForm form = OperatorForm.parseOperatorForm(formStr);
209 final String lspace = (String) lspaceExpr.evaluate(trNode,
210 XPathConstants.STRING);
211 final String rspace = (String) rspaceExpr.evaluate(trNode,
212 XPathConstants.STRING);
213 final String minsize = (String) minsizeExpr.evaluate(trNode,
214 XPathConstants.STRING);
215 final String propertiesString = (String) propertiesExpr.evaluate(
216 trNode, XPathConstants.STRING);
217
218 this.addAttr(operator, form, OperatorAttribute.LSPACE, this
219 .intToSpace(lspace), dict);
220 this.addAttr(operator, form, OperatorAttribute.RSPACE, this
221 .intToSpace(rspace), dict);
222 if (minsize.length() > 0) {
223 this.addAttr(operator, form, OperatorAttribute.MINSIZE,
224 minsize, dict);
225 }
226 this.addProperties(operator, form, propertiesString, dict);
227 }
228 }
229
230 /**
231 * @return
232 * @throws SAXException
233 * @throws IOException
234 */
235 private Document loadDocument() throws SAXException, IOException {
236 final InputStream is = OperatorDictionary3.class
237 .getResourceAsStream(OperatorDictionary3.DICTIONARY_FILE);
238 final Document doc = Parser.getInstance().getDocumentBuilder().parse(
239 is);
240 return doc;
241 }
242
243 /**
244 * @return
245 */
246 private XPath createXPath() {
247 final XPathFactory factory = XPathFactory.newInstance();
248 final XPath xpath = factory.newXPath();
249 final NamespaceContext xml = new NamespaceContextAdder("xml",
250 XMLConstants.XML_NS_URI, null);
251 final NamespaceContext html = new NamespaceContextAdder("html",
252 "http://www.w3.org/1999/xhtml", xml);
253 xpath.setNamespaceContext(html);
254 return xpath;
255 }
256
257 /**
258 * @param operator
259 * @param form
260 * @param propertiesString
261 */
262 private void addProperties(
263 final String operator,
264 final OperatorForm form,
265 final String propertiesString,
266 final Map<OperatorAttribute, Map<String, Map<OperatorForm, String>>> dict) {
267 final String[] properties = propertiesString.split(" ");
268 for (final String property : properties) {
269 if (property.length() > 0) {
270 try {
271 final OperatorAttribute oa = OperatorAttribute
272 .parseOperatorAttribute(property);
273 this.addAttr(operator, form, oa, Constants.TRUE, dict);
274 } catch (final UnknownAttributeException uae) {
275 OperatorDictionary3.LOGGER.warn(
276 "Unkown Attribute when reading operator dictionary: "
277 + property, uae);
278 }
279 }
280 }
281 }
282
283 /**
284 * @param operator
285 * @param form
286 * @param lspace
287 * @param intToSpace
288 */
289 private void addAttr(
290 final String operator,
291 final OperatorForm form,
292 final OperatorAttribute attribute,
293 final String value,
294 final Map<OperatorAttribute, Map<String, Map<OperatorForm, String>>> dict) {
295
296 Map<String, Map<OperatorForm, String>> innerMap1 = dict
297 .get(attribute);
298 if (innerMap1 == null) {
299 innerMap1 = new HashMap<String, Map<OperatorForm, String>>();
300 dict.put(attribute, innerMap1);
301 }
302
303 Map<OperatorForm, String> innerMap2 = innerMap1.get(operator);
304 if (innerMap2 == null) {
305 innerMap2 = new EnumMap<OperatorForm, String>(OperatorForm.class);
306 innerMap1.put(operator, innerMap2);
307 }
308
309 innerMap2.put(form, value);
310 }
311
312 /**
313 * @param lspace
314 * @return
315 */
316 private String intToSpace(final String spaceInt) {
317 String retVal;
318 try {
319 final int i = Integer.parseInt(spaceInt);
320 switch (i) {
321 case OperatorDictionary3.INT_NO_SPACE:
322 retVal = OperatorDictionary3.NO_SPACE;
323 break;
324 case OperatorDictionary3.INT_VERYVERYTHINMATHSPACE:
325 retVal = OperatorDictionary.NAME_VERYVERYTHINMATHSPACE;
326 break;
327 case OperatorDictionary3.INT_VERYTHINMATHSPACE:
328 retVal = OperatorDictionary.NAME_VERYTHINMATHSPACE;
329 break;
330 case OperatorDictionary3.INT_THINMATHSPACE:
331 retVal = OperatorDictionary.NAME_THINMATHSPACE;
332 break;
333 case OperatorDictionary3.INT_MEDIUMMATHSPACE:
334 retVal = OperatorDictionary.NAME_MEDIUMMATHSPACE;
335 break;
336 case OperatorDictionary3.INT_THICKMATHSPACE:
337 retVal = OperatorDictionary.NAME_THICKMATHSPACE;
338 break;
339 case OperatorDictionary3.INT_VERYTHICKMATHSPACE:
340 retVal = OperatorDictionary.NAME_VERYTHICKMATHSPACE;
341 break;
342 case OperatorDictionary3.INT_VERYVERYTHICKMATHSPACE:
343 retVal = OperatorDictionary.NAME_VERYVERYTHICKMATHSPACE;
344 break;
345 default:
346 retVal = OperatorDictionary3.NO_SPACE;
347 }
348 } catch (final NumberFormatException e) {
349 retVal = OperatorDictionary3.NO_SPACE;
350 }
351 return retVal;
352 }
353
354 }