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    }