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 }