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 }