001 package com.thaiopensource.relaxng.impl;
002
003 import java.util.HashMap;
004 import java.util.HashSet;
005 import java.util.Hashtable;
006 import java.util.Iterator;
007 import java.util.Map;
008 import java.util.Set;
009
010 import nu.validator.relaxng.exceptions.BadAttributeValueException;
011 import nu.validator.relaxng.exceptions.ImpossibleAttributeIgnoredException;
012 import nu.validator.relaxng.exceptions.OnlyTextNotAllowedException;
013 import nu.validator.relaxng.exceptions.OutOfContextElementException;
014 import nu.validator.relaxng.exceptions.RequiredAttributesMissingException;
015 import nu.validator.relaxng.exceptions.RequiredElementsMissingException;
016 import nu.validator.relaxng.exceptions.StringNotAllowedException;
017 import nu.validator.relaxng.exceptions.TextNotAllowedException;
018 import nu.validator.relaxng.exceptions.UnfinishedElementException;
019 import nu.validator.relaxng.exceptions.UnknownElementException;
020
021 import org.relaxng.datatype.DatatypeException;
022 import org.xml.sax.Attributes;
023 import org.xml.sax.ContentHandler;
024 import org.xml.sax.DTDHandler;
025 import org.xml.sax.ErrorHandler;
026 import org.xml.sax.Locator;
027 import org.xml.sax.SAXException;
028 import org.xml.sax.SAXParseException;
029
030 import com.thaiopensource.relaxng.parse.sax.DtdContext;
031 import com.thaiopensource.validate.Validator;
032 import com.thaiopensource.xml.util.Name;
033 import com.thaiopensource.xml.util.WellKnownNamespaces;
034
035 public class PatternValidator extends DtdContext implements Validator, ContentHandler, DTDHandler {
036 private final ValidatorPatternBuilder builder;
037 private final Pattern start;
038 private final ErrorHandler eh;
039 private Hashtable recoverPatternTable;
040 private PatternMemo memo;
041 private boolean hadError;
042 private boolean collectingCharacters;
043 private final StringBuffer charBuf = new StringBuffer();
044 private PrefixMapping prefixMapping = new PrefixMapping("xml", WellKnownNamespaces.XML, null);
045 private Locator locator;
046 private final Map datatypeErrors = new HashMap();
047 private Name[] stack = null;
048 private int stackLen = 0;
049 private int suppressDepth = 0;
050
051 private static final class PrefixMapping {
052 private final String prefix;
053 private final String namespaceURI;
054 private final PrefixMapping previous;
055
056 PrefixMapping(String prefix, String namespaceURI, PrefixMapping prev) {
057 this.prefix = prefix;
058 this.namespaceURI = namespaceURI;
059 this.previous = prev;
060 }
061
062 PrefixMapping getPrevious() {
063 return previous;
064 }
065 }
066
067 private void startCollectingCharacters() {
068 if (!collectingCharacters) {
069 collectingCharacters = true;
070 charBuf.setLength(0);
071 }
072 }
073
074 private void flushCharacters() throws SAXException {
075 collectingCharacters = false;
076 int len = charBuf.length();
077 for (int i = 0; i < len; i++) {
078 switch (charBuf.charAt(i)) {
079 case ' ':
080 case '\r':
081 case '\t':
082 case '\n':
083 break;
084 default:
085 text();
086 return;
087 }
088 }
089 }
090
091 public void startElement(String namespaceURI,
092 String localName,
093 String qName,
094 Attributes atts) throws SAXException {
095 if (collectingCharacters)
096 flushCharacters();
097 if (suppressDepth > 0) {
098 suppressDepth++;
099 }
100
101 Name name = new Name(namespaceURI, localName);
102 if (!setMemo(memo.startTagOpenDeriv(name))) {
103 PatternMemo next = memo.startTagOpenRecoverDeriv(name);
104 if (!next.isNotAllowed())
105 error(new RequiredElementsMissingException(locator, name, peek()));
106 else {
107 next = builder.getPatternMemo(builder.makeAfter(findElement(name), memo.getPattern()));
108 if (next.isNotAllowed()) {
109 error(new UnknownElementException(locator, name, peek()));
110 } else {
111 error(new OutOfContextElementException(locator, name, peek()));
112 }
113 if (suppressDepth == 0) {
114 suppressDepth = 1;
115 }
116 }
117 memo = next;
118 }
119 int len = atts.getLength();
120 for (int i = 0; i < len; i++) {
121 Name attName = new Name(atts.getURI(i), atts.getLocalName(i));
122 String value = atts.getValue(i);
123 datatypeErrors.clear();
124 if (!setMemo(memo.startAttributeDeriv(attName)))
125 error(new ImpossibleAttributeIgnoredException(locator, name, peek(), attName));
126 else if (!setMemo(memo.dataDeriv(value, this))) {
127 error(new BadAttributeValueException(locator, name, peek(), attName, value, datatypeErrors));
128 memo = memo.recoverAfter();
129 }
130 }
131 if (!setMemo(memo.endAttributes())) {
132 // XXX should specify which attributes
133 error(new RequiredAttributesMissingException(locator, name, peek()));
134 memo = memo.ignoreMissingAttributes();
135 }
136 if (memo.getPattern().getContentType() == Pattern.DATA_CONTENT_TYPE)
137 startCollectingCharacters();
138 push(name);
139 }
140
141 private PatternMemo fixAfter(PatternMemo p) {
142 return builder.getPatternMemo(p.getPattern().applyForPattern(new ApplyAfterFunction(builder) {
143 Pattern apply(Pattern p) {
144 return builder.makeEmpty();
145 }
146 }));
147 }
148
149 public void endElement(String namespaceURI,
150 String localName,
151 String qName) throws SAXException {
152 Name name = pop();
153 // The tricky thing here is that the derivative that we compute may be notAllowed simply because the parent
154 // is notAllowed; we don't want to give an error in this case.
155 if (collectingCharacters) {
156 collectingCharacters = false;
157 if (!setMemo(memo.textOnly())) {
158 error(new OnlyTextNotAllowedException(locator, name, peek()));
159 memo = memo.recoverAfter();
160 return;
161 }
162 final String data = charBuf.toString();
163 if (!setMemo(memo.dataDeriv(data, this))) {
164 PatternMemo next = memo.recoverAfter();
165 datatypeErrors.clear();
166 if (!memo.isNotAllowed()) {
167 if (!next.isNotAllowed()
168 || fixAfter(memo).dataDeriv(data, this).isNotAllowed())
169 error(new StringNotAllowedException(locator, name, peek(), data, datatypeErrors));
170 }
171 memo = next;
172 }
173 }
174 else if (!setMemo(memo.endTagDeriv())) {
175 PatternMemo next = memo.recoverAfter();
176 if (!memo.isNotAllowed()) {
177 if (!next.isNotAllowed()
178 || fixAfter(memo).endTagDeriv().isNotAllowed())
179 error(new UnfinishedElementException(locator, name, peek()));
180 }
181 memo = next;
182 }
183 if (suppressDepth > 0) {
184 suppressDepth--;
185 }
186 }
187
188 public void characters(char ch[], int start, int length) throws SAXException {
189 if (collectingCharacters) {
190 charBuf.append(ch, start, length);
191 return;
192 }
193 for (int i = 0; i < length; i++) {
194 switch (ch[start + i]) {
195 case ' ':
196 case '\r':
197 case '\t':
198 case '\n':
199 break;
200 default:
201 text();
202 return;
203 }
204 }
205 }
206
207 private void text() throws SAXException {
208 if (!setMemo(memo.mixedTextDeriv()))
209 error(new TextNotAllowedException(locator, peek()));
210 }
211
212 public void endDocument() {
213 // XXX maybe check that memo.isNullable if !hadError
214 stack = null;
215 }
216
217 public void setDocumentLocator(Locator loc) {
218 locator = loc;
219 }
220
221 public void startDocument() throws SAXException {
222 stack = new Name[48];
223 stackLen = 0;
224 suppressDepth = 0;
225 if (memo.isNotAllowed())
226 error("schema_allows_nothing");
227 }
228 public void processingInstruction(String target, String date) { }
229 public void skippedEntity(String name) { }
230 public void ignorableWhitespace(char[] ch, int start, int len) { }
231 public void startPrefixMapping(String prefix, String uri) {
232 prefixMapping = new PrefixMapping(prefix, uri, prefixMapping);
233 }
234 public void endPrefixMapping(String prefix) {
235 prefixMapping = prefixMapping.getPrevious();
236 }
237
238 public PatternValidator(Pattern pattern, ValidatorPatternBuilder builder, ErrorHandler eh) {
239 this.start = pattern;
240 this.builder = builder;
241 this.eh = eh;
242 reset();
243 }
244
245 public void reset() {
246 hadError = false;
247 collectingCharacters = false;
248 locator = null;
249 memo = builder.getPatternMemo(start);
250 prefixMapping = new PrefixMapping("xml", WellKnownNamespaces.XML, null);
251 clearDtdContext();
252 }
253
254 public ContentHandler getContentHandler() {
255 return this;
256 }
257
258 public DTDHandler getDTDHandler() {
259 return this;
260 }
261
262 private void error(String key) throws SAXException {
263 if ((suppressDepth > 0) || (hadError && memo.isNotAllowed()))
264 return;
265 hadError = true;
266 eh.error(new SAXParseException(SchemaBuilderImpl.localizer.message(key), locator));
267 }
268
269 private void error(SAXParseException e) throws SAXException {
270 if ((suppressDepth > 0) || (hadError && memo.isNotAllowed()))
271 return;
272 hadError = true;
273 eh.error(e);
274 }
275
276 /* Return false if m is notAllowed. */
277 private boolean setMemo(PatternMemo m) {
278 if (m.isNotAllowed())
279 return false;
280 else {
281 memo = m;
282 return true;
283 }
284 }
285
286 private Pattern findElement(Name name) {
287 if (recoverPatternTable == null)
288 recoverPatternTable = new Hashtable();
289 Pattern p = (Pattern)recoverPatternTable.get(name);
290 if (p == null) {
291 p = FindElementFunction.findElement(builder, name, start);
292 recoverPatternTable.put(name, p);
293 }
294 return p;
295 }
296
297 public String resolveNamespacePrefix(String prefix) {
298 PrefixMapping tem = prefixMapping;
299 do {
300 if (tem.prefix.equals(prefix))
301 return tem.namespaceURI;
302 tem = tem.previous;
303 } while (tem != null);
304 return null;
305 }
306
307 public String getBaseUri() {
308 return null;
309 }
310
311 public final void addDatatypeError(String message, DatatypeException exception) {
312 datatypeErrors.put(message, exception);
313 }
314
315 private final void push(Name name) {
316 if (stackLen == stack.length) {
317 Name[] newStack = new Name[stack.length + 48];
318 System.arraycopy(stack, 0, newStack, 0, stack.length);
319 stack = newStack;
320 }
321 stack[stackLen] = name;
322 stackLen++;
323 }
324
325 private final Name pop() {
326 stackLen--;
327 return stack[stackLen];
328 }
329
330 private final Name peek() {
331 if (stackLen == 0) {
332 return null;
333 } else {
334 return stack[stackLen - 1];
335 }
336 }
337 }