001 package com.thaiopensource.relaxng; 002 003 import com.thaiopensource.validate.auto.AutoSchemaReader; 004 import com.thaiopensource.util.PropertyMapBuilder; 005 import com.thaiopensource.validate.Flag; 006 import com.thaiopensource.validate.IncorrectSchemaException; 007 import com.thaiopensource.validate.SchemaReader; 008 import com.thaiopensource.xml.sax.XMLReaderCreator; 009 import com.thaiopensource.validate.Schema; 010 import com.thaiopensource.validate.ValidateProperty; 011 import com.thaiopensource.validate.rng.CompactSchemaReader; 012 import com.thaiopensource.validate.rng.RngProperty; 013 import org.relaxng.datatype.DatatypeLibraryFactory; 014 import org.xml.sax.ErrorHandler; 015 import org.xml.sax.InputSource; 016 import org.xml.sax.SAXException; 017 018 import java.io.IOException; 019 020 /** 021 * A factory for RELAX NG schemas. The factory creates <code>Schema</code> objects from their 022 * XML representation. 023 * 024 * A single <code>SchemaFactory</code> is <em>not</em> safe for concurrent 025 * access by multiple threads; it must be accessed by at most one thread at a time. 026 * Schemas can be created concurrently by using a distinct <code>SchemaFactory</code> for each 027 * thread. However, the <code>Schema</code> objects created <em>are</em> safe for concurrent 028 * access by multiple threads. 029 * 030 * @author <a href="mailto:jjc@jclark.com">James Clark</a> 031 */ 032 public class SchemaFactory { 033 private PropertyMapBuilder properties = new PropertyMapBuilder(); 034 private boolean compactSyntax = false; 035 private SchemaReader autoSchemaLanguage = new AutoSchemaReader(); 036 037 /** 038 * Constructs a schema factory. 039 */ 040 public SchemaFactory() { 041 } 042 043 /** 044 * Creates a schema by parsing an XML document. A non-null <code>XMLReaderCreator</code> must be specified 045 * with <code>setXMLReaderCreator</code> before calling <code>createSchema</code>. The <code>ErrorHandler</code> 046 * is allowed to be <code>null</code>. The <code>DatatypeLibraryFactory</code> is allowed to be <code>null</code>. 047 * 048 * <p>Normally, if a schema cannot be created, <code>createSchema</code> will throw 049 * a <code>IncorrectSchemaException</code>; however, 050 * before doing so, one or more errors will be reported using the <code>ErrorHandler</code> if it is non-null. If the 051 * <code>ErrorHandler</code> throws a <code>SAXException</code>, then <code>createSchema</code> will pass this 052 * through rather than throwing a <code>IncorrectSchemaException</code>. Similarly, if <code>XMLReader.parse</code> 053 * throws a <code>SAXException</code> or <code>IOException</code>, then <code>createSchema</code> will pass 054 * this through rather than throwing a <code>IncorrectSchemaException</code>. Thus, if an error handler 055 * is specified that reports errors to the user, there is no need to report any additional message to the 056 * user if <code>createSchema</code> throws <code>IncorrectSchemaException</code>. 057 * 058 * @param in the <code>InputSource</code> containing the XML document to be parsed; 059 * must not be <code>null</code> 060 * @return the <code>Schema</code> constructed from the XML document; 061 * never <code>null</code>. 062 * 063 * @throws IOException if an I/O error occurs 064 * @throws SAXException if there is an XML parsing error and the XMLReader or ErrorHandler 065 * throws a SAXException 066 * @throws com.thaiopensource.validate.IncorrectSchemaException if the XML document was not a correct RELAX NG schema 067 * @throws NullPointerException if the current XMLReaderCreator is <code>null</code> 068 */ 069 public Schema createSchema(InputSource in) throws IOException, SAXException, IncorrectSchemaException { 070 SchemaReader r = compactSyntax ? CompactSchemaReader.getInstance() : autoSchemaLanguage; 071 return r.createSchema(in, properties.toPropertyMap()); 072 } 073 074 /** 075 * Specifies the XMLReaderCreator to be used for creating <code>XMLReader</code>s for parsing 076 * the XML document. Because of <code>include</code> and <code>externalRef</code> elements, 077 * parsing a single RELAX NG may require the creation of multiple more than one <code>XMLReader</code>. 078 * A non-null XMLReaderCreator must be specified before calling <code>createSchema</code>. 079 * 080 * @param xrc the <code>XMLReaderCreator</code> to be used for parsing the XML document containing 081 * the schema; may be <code>null</code> 082 * @see #getXMLReaderCreator 083 */ 084 public void setXMLReaderCreator(XMLReaderCreator xrc) { 085 properties.put(ValidateProperty.XML_READER_CREATOR, xrc); 086 } 087 088 /** 089 * Returns the current <code>XMLReaderCreator</code> as specified by <code>setXMLReaderCreator</code>. 090 * If <code>XMLReaderCreator</code> has never been called, then <code>getXMLReaderCreator</code> 091 * returns null. 092 * 093 * @return the <code>XMLReaderCreator</code> that will be used for parsing the XML document containing 094 * the schema; may be <code>null</code> 095 * 096 * @see #setXMLReaderCreator 097 */ 098 public XMLReaderCreator getXMLReaderCreator() { 099 return (XMLReaderCreator)properties.get(ValidateProperty.XML_READER_CREATOR); 100 } 101 102 /** 103 * Specifies the <code>ErrorHandler</code> to be used for reporting errors while creating the schema. 104 * This does not affect the error handler used for validation. 105 * 106 * @param eh the <code>ErrorHandler</code> to be used for reporting errors while creating the schema; 107 * may be <code>null</code>. 108 * @see #getErrorHandler 109 */ 110 public void setErrorHandler(ErrorHandler eh) { 111 properties.put(ValidateProperty.ERROR_HANDLER, eh); 112 } 113 114 /** 115 * Returns the <code>ErrorHandler</code> that will be used for reporting errors while creating the 116 * schema. If <code>setErrorHandler</code> has not been called for this <code>SchemaFactory</code>, 117 * then <code>getErrorHandler</code> returns <code>null</code>. 118 * 119 * @return the <code>ErrorHandler</code> to be used for reporting errors while creating the schema; 120 * may be <code>null</code>. 121 * @see #setErrorHandler 122 */ 123 public ErrorHandler getErrorHandler() { 124 return (ErrorHandler)properties.get(ValidateProperty.ERROR_HANDLER); 125 } 126 127 /** 128 * Specifies the <code>DatatypeLibraryFactory</code> to be used for handling datatypes in the schema. 129 * This also determines how datatypes are handled during validation. If <code>null</code> is 130 * specified then only the builtin datatypes will be supported. 131 * 132 * @param dlf the <code>DatatypeLibraryFactory</code> to be used for handling datatypes in the schema 133 * @see #getDatatypeLibraryFactory 134 */ 135 public void setDatatypeLibraryFactory(DatatypeLibraryFactory dlf) { 136 properties.put(RngProperty.DATATYPE_LIBRARY_FACTORY, dlf); 137 } 138 139 /** 140 * Returns the <code>DatatypeLibraryFactory</code> that will be used for handling datatypes in the 141 * schema. If <code>setDatatypeLibraryFactory</code> has not been called for this <code>SchemaFactory</code>, 142 * then <code>getDatatypeLibraryFactory</code> returns <code>null</code>. 143 * 144 * @return the <code>DatatypeLibraryFactory</code> to be used for handling datatypes in the schema; 145 * may be null. 146 * @see #setDatatypeLibraryFactory 147 */ 148 public DatatypeLibraryFactory getDatatypeLibraryFactory() { 149 return (DatatypeLibraryFactory)properties.get(RngProperty.DATATYPE_LIBRARY_FACTORY); 150 } 151 152 /** 153 * Specifies whether to perform checking of ID/IDREF/IDREFS attributes in accordance with 154 * RELAX NG DTD Compatibility. 155 * 156 * @param checkIdIdref <code>true</code> if ID/IDREF/IDREFS checking should be performed; 157 * <code>false</code> otherwise 158 * 159 * @see #getCheckIdIdref 160 * @see <a href="http://www.oasis-open.org/committees/relax-ng/compatibility.html#id">RELAX NG DTD Compatibility</a> 161 */ 162 public void setCheckIdIdref(boolean checkIdIdref) { 163 properties.put(RngProperty.CHECK_ID_IDREF, checkIdIdref ? Flag.PRESENT : null); 164 } 165 166 /** 167 * Indicates whether ID/IDREF/IDREFS attributes will be checked in accordance RELAX NG DTD 168 * Compatibility. If <code>setCheckIdIdref</code> has not been called for this <code>SchemaFactory</code>, 169 * then <code>getCheckIdref</code> will return <code>false</code>. 170 * 171 * @return <code>true</code> if ID/IDREF/IDREFS attributes will be checked; 172 * <code>false</code> otherwise. 173 * 174 * @see #setCheckIdIdref 175 * @see <a href="http://www.oasis-open.org/committees/relax-ng/compatibility.html#id">RELAX NG DTD Compatibility</a> 176 */ 177 public boolean getCheckIdIdref() { 178 return properties.contains(RngProperty.CHECK_ID_IDREF); 179 } 180 181 /** 182 * Specifies whether to use the compact syntax to parse the RELAX NG schema rather than the normal XML syntax. 183 * 184 * @param compactSyntax <code>true</code> if the compact syntax should be used; <code>false</code> 185 * if the XML syntax should be used 186 * @see #getCompactSyntax 187 */ 188 public void setCompactSyntax(boolean compactSyntax) { 189 this.compactSyntax = compactSyntax; 190 } 191 192 /** 193 * Indicates whether the compact syntax will be used to parse the RELAX NG schema rather than 194 * the normal XML syntax. 195 * 196 * @return <code>true</code> if the compact syntax will be used; <code>false</code> if the XML 197 * syntax will be used 198 */ 199 public boolean getCompactSyntax() { 200 return compactSyntax; 201 } 202 203 public void setFeasible(boolean feasible) { 204 properties.put(RngProperty.FEASIBLE, feasible ? Flag.PRESENT : null); 205 } 206 207 public boolean getFeasible() { 208 return properties.contains(RngProperty.FEASIBLE); 209 } 210 }