2 // Commons.Xml.Relaxng.RelaxngValidatingReader
5 // Atsushi Enomoto <ginga@kit.hi-ho.ne.jp>
7 // 2003 Atsushi Enomoto. "No rights reserved."
9 // Copyright (c) 2004 Novell Inc.
10 // All rights reserved
14 // Permission is hereby granted, free of charge, to any person obtaining
15 // a copy of this software and associated documentation files (the
16 // "Software"), to deal in the Software without restriction, including
17 // without limitation the rights to use, copy, modify, merge, publish,
18 // distribute, sublicense, and/or sell copies of the Software, and to
19 // permit persons to whom the Software is furnished to do so, subject to
20 // the following conditions:
22 // The above copyright notice and this permission notice shall be
23 // included in all copies or substantial portions of the Software.
25 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
26 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 using System.Collections;
37 using Commons.Xml.Relaxng.Derivative;
39 namespace Commons.Xml.Relaxng
41 public class RelaxngValidatingReader : XmlDefaultReader
43 public RelaxngValidatingReader (XmlReader reader)
44 : this (reader, (RelaxngPattern) null)
48 public RelaxngValidatingReader (XmlReader reader, XmlReader grammarXml)
49 : this (reader, grammarXml, null)
53 public RelaxngValidatingReader (XmlReader reader, XmlReader grammarXml, RelaxngDatatypeProvider provider)
54 : this (reader, RelaxngGrammar.Read (grammarXml, provider))
58 public RelaxngValidatingReader (XmlReader reader, RelaxngPattern pattern)
61 if (reader.NodeType == XmlNodeType.Attribute)
62 throw new RelaxngException ("RELAX NG does not support standalone attribute validation (it is prohibited due to the specification section 7.1.5");
64 this.pattern = pattern;
68 RelaxngPattern pattern;
70 RdpPattern prevState; // Mainly for debugging.
71 ArrayList PredefinedAttributes = new ArrayList ();
73 Hashtable elementLabels = new Hashtable ();
74 Hashtable attributeLabels = new Hashtable ();
77 ArrayList strictCheckCache;
81 internal string CurrentStateXml {
82 get { return RdpUtil.DebugRdpPattern (vState, new Hashtable ()); }
85 internal string PreviousStateXml {
86 get { return RdpUtil.DebugRdpPattern (prevState, new Hashtable ()); }
89 #region Validation State support
91 public bool ReportDetails {
92 get { return reportDetails; }
93 set { reportDetails = value; }
96 public bool RoughLabelCheck {
97 get { return roughLabelCheck; }
98 set { roughLabelCheck = value; }
101 // It is used to disclose its validation feature to public
102 class ValidationState
106 internal ValidationState (RdpPattern startState)
108 this.state = startState;
111 public RdpPattern Pattern {
112 get { return state; }
115 public ValidationState AfterOpenStartTag (
116 string localName, string ns)
118 RdpPattern p = state.StartTagOpenDeriv (
120 return p is RdpNotAllowed ?
121 null : new ValidationState (p);
124 public bool OpenStartTag (string localName, string ns)
126 RdpPattern p = state.StartTagOpenDeriv (
128 if (p is RdpNotAllowed)
134 public ValidationState AfterCloseStartTag ()
136 RdpPattern p = state.StartTagCloseDeriv ();
137 return p is RdpNotAllowed ?
138 null : new ValidationState (p);
141 public bool CloseStartTag ()
143 RdpPattern p = state.StartTagCloseDeriv ();
144 if (p is RdpNotAllowed)
150 public ValidationState AfterEndTag ()
152 RdpPattern p = state.EndTagDeriv ();
153 if (p is RdpNotAllowed)
155 return new ValidationState (p);
158 public bool EndTag ()
160 RdpPattern p = state.EndTagDeriv ();
161 if (p is RdpNotAllowed)
167 public ValidationState AfterAttribute (
168 string localName, string ns, XmlReader reader)
170 RdpPattern p = state.AttDeriv (
171 localName, ns, null, reader);
172 if (p is RdpNotAllowed)
174 return new ValidationState (p);
177 public bool Attribute (
178 string localName, string ns, XmlReader reader)
180 RdpPattern p = state.AttDeriv (
181 localName, ns, null, reader);
182 if (p is RdpNotAllowed)
189 public object GetCurrentState ()
192 return new ValidationState (vState);
195 private ValidationState ToState (object stateObject)
197 if (stateObject == null)
198 throw new ArgumentNullException ("stateObject");
199 ValidationState state = stateObject as ValidationState;
201 throw new ArgumentException ("Argument stateObject is not of expected type.");
205 public object AfterOpenStartTag (object stateObject,
206 string localName, string ns)
208 ValidationState state = ToState (stateObject);
209 return state.AfterOpenStartTag (localName, ns);
212 public bool OpenStartTag (object stateObject,
213 string localName, string ns)
215 ValidationState state = ToState (stateObject);
216 return state.OpenStartTag (localName, ns);
219 public object AfterAttribute (object stateObject,
220 string localName, string ns)
222 ValidationState state = ToState (stateObject);
223 return state.AfterAttribute (localName, ns, this);
226 public bool Attribute (object stateObject,
227 string localName, string ns)
229 ValidationState state = ToState (stateObject);
230 return state.Attribute (localName, ns, this);
233 public object AfterCloseStartTag (object stateObject)
235 ValidationState state = ToState (stateObject);
236 return state.AfterCloseStartTag ();
239 public bool CloseStartTag (object stateObject)
241 ValidationState state = ToState (stateObject);
242 return state.CloseStartTag ();
245 public object AfterEndTag (object stateObject)
247 ValidationState state = ToState (stateObject);
248 return state.AfterEndTag ();
251 public bool EndTag (object stateObject)
253 ValidationState state = ToState (stateObject);
254 return state.EndTag ();
257 public ICollection GetElementLabels (object stateObject)
259 ValidationState state = ToState (stateObject);
260 RdpPattern p = state.Pattern;
261 Hashtable elements = new Hashtable ();
262 Hashtable attributes = new Hashtable ();
263 p.GetLabels (elements, attributes);
266 return elements.Values;
268 // Strict check that tries actual validation that will
269 // cover rejection by notAllowed.
270 if (strictCheckCache == null)
271 strictCheckCache = new ArrayList ();
273 strictCheckCache.Clear ();
274 foreach (XmlQualifiedName qname in elements.Values)
275 if (p.StartTagOpenDeriv (qname.Name, qname.Namespace) is RdpNotAllowed)
276 strictCheckCache.Add (qname);
277 foreach (XmlQualifiedName qname in strictCheckCache)
278 elements.Remove (qname);
279 strictCheckCache.Clear ();
281 return elements.Values;
284 public ICollection GetAttributeLabels (object stateObject)
286 ValidationState state = ToState (stateObject);
287 RdpPattern p = state.Pattern;
288 Hashtable elements = new Hashtable ();
289 Hashtable attributes = new Hashtable ();
290 p.GetLabels (elements, attributes);
293 return attributes.Values;
295 // Strict check that tries actual validation that will
296 // cover rejection by notAllowed.
297 if (strictCheckCache == null)
298 strictCheckCache = new ArrayList ();
300 strictCheckCache.Clear ();
301 foreach (XmlQualifiedName qname in attributes.Values)
302 if (p.AttDeriv (qname.Name, qname.Namespace,null, this) is RdpNotAllowed)
303 strictCheckCache.Add (qname);
304 foreach (XmlQualifiedName qname in strictCheckCache)
305 attributes.Remove (qname);
306 strictCheckCache.Clear ();
308 return attributes.Values;
311 public bool Emptiable (object stateObject)
313 ValidationState state = ToState (stateObject);
314 RdpPattern p = state.Pattern;
315 return !(p.EndTagDeriv () is RdpNotAllowed);
319 private RelaxngException createValidationError (string message)
321 IXmlLineInfo li = reader as IXmlLineInfo;
322 string lineInfo = reader.BaseURI;
324 lineInfo += String.Format (" line {0}, column {1}",
325 li.LineNumber, li.LinePosition);
326 return new RelaxngException (message + lineInfo, prevState);
329 private void PrepareState ()
331 if (!pattern.IsCompiled) {
335 vState = pattern.StartPattern;
338 private string BuildLabels (bool elements)
340 StringBuilder sb = new StringBuilder ();
341 ValidationState s = new ValidationState (prevState);
342 ICollection col = elements ?
343 GetElementLabels (s) : GetAttributeLabels (s);
344 foreach (XmlQualifiedName qname in col) {
345 sb.Append (qname.ToString ());
348 return sb.ToString ();
351 public override bool Read ()
355 labelsComputed = false;
356 elementLabels.Clear ();
357 attributeLabels.Clear ();
359 bool ret = reader.Read ();
361 // Process pending text node validation if required.
362 if (cachedValue != null) {
363 switch (reader.NodeType) {
364 case XmlNodeType.Element:
365 case XmlNodeType.EndElement:
367 vState = vState.TextDeriv (cachedValue, reader);
368 if (vState.PatternType == RelaxngPatternType.NotAllowed)
369 throw createValidationError (String.Format ("Invalid text found. Text value = {0} ", cachedValue));
374 goto case XmlNodeType.Element;
379 switch (reader.NodeType) {
380 case XmlNodeType.Element:
383 vState = vState.StartTagOpenDeriv (
384 reader.LocalName, reader.NamespaceURI);
385 if (vState.PatternType == RelaxngPatternType.NotAllowed) {
386 string labels = String.Empty;
388 labels = "Allowed elements are: " + BuildLabels (true);
389 throw createValidationError (String.Format ("Invalid start tag found. LocalName = {0}, NS = {1}. {2}", reader.LocalName, reader.NamespaceURI, labels));
392 // AttsDeriv equals to for each AttDeriv
393 string elementNS = reader.NamespaceURI;
394 if (reader.MoveToFirstAttribute ()) {
396 if (reader.Name.IndexOf ("xmlns:") == 0 || reader.Name == "xmlns")
400 string attrNS = reader.NamespaceURI;
401 vState = vState.AttDeriv (reader.LocalName, attrNS, reader.GetAttribute (reader.LocalName, attrNS), this);
402 if (vState.PatternType == RelaxngPatternType.NotAllowed) {
403 string labels = String.Empty;
405 labels = "Allowed attributes are: " + BuildLabels (false);
407 throw createValidationError (String.Format ("Invalid attribute found. LocalName = {0}, NS = {1}. {2}", reader.LocalName, reader.NamespaceURI, labels));
409 } while (reader.MoveToNextAttribute ());
415 vState = vState.StartTagCloseDeriv ();
416 if (vState.PatternType == RelaxngPatternType.NotAllowed) {
417 string labels = String.Empty;
419 labels = "Expected attributes are: " + BuildLabels (false);
421 throw createValidationError (String.Format ("Invalid start tag closing found. LocalName = {0}, NS = {1}. {2}", reader.LocalName, reader.NamespaceURI, labels));
424 // if it is empty, then redirect to EndElement
425 if (reader.IsEmptyElement)
426 goto case XmlNodeType.EndElement;
428 case XmlNodeType.EndElement:
431 vState = vState.EndTagDeriv ();
432 if (vState.PatternType == RelaxngPatternType.NotAllowed) {
433 string labels = String.Empty;
435 labels = "Expected elements are: " + BuildLabels (true);
436 throw createValidationError (String.Format ("Invalid end tag found. LocalName = {0}, NS = {1}. {2}", reader.LocalName, reader.NamespaceURI, labels));
439 case XmlNodeType.CDATA:
440 case XmlNodeType.Text:
441 case XmlNodeType.SignificantWhitespace:
442 case XmlNodeType.Whitespace:
443 // Whitespace cannot be skipped because data and
444 // value types are required to validate whitespaces.
445 cachedValue += Value;