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 }