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    }