2 // Commons.Xml.Relaxng.RelaxngValidatingReader
\r
5 // Atsushi Enomoto <ginga@kit.hi-ho.ne.jp>
\r
7 // 2003 Atsushi Enomoto. "No rights reserved."
\r
9 // Copyright (c) 2004 Novell Inc.
\r
10 // All rights reserved
\r
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;
\r
37 using Commons.Xml.Relaxng.Derivative;
\r
39 namespace Commons.Xml.Relaxng
\r
41 public class RelaxngValidatingReader : XmlDefaultReader
\r
43 public RelaxngValidatingReader (XmlReader reader)
\r
44 : this (reader, (RelaxngPattern) null)
\r
48 public RelaxngValidatingReader (XmlReader reader, XmlReader grammarXml)
\r
49 : this (reader, grammarXml, null)
\r
53 public RelaxngValidatingReader (XmlReader reader, XmlReader grammarXml, RelaxngDatatypeProvider provider)
\r
54 : this (reader, RelaxngGrammar.Read (grammarXml, provider))
\r
58 public RelaxngValidatingReader (XmlReader reader, RelaxngPattern pattern)
\r
61 if (reader.NodeType == XmlNodeType.Attribute)
\r
62 throw new RelaxngException ("RELAX NG does not support standalone attribute validation (it is prohibited due to the specification section 7.1.5");
\r
63 this.reader = reader;
\r
64 this.pattern = pattern;
\r
68 RelaxngPattern pattern;
\r
70 RdpPattern prevState; // Mainly for debugging.
\r
71 ArrayList PredefinedAttributes = new ArrayList ();
\r
72 bool labelsComputed;
\r
73 Hashtable elementLabels = new Hashtable ();
\r
74 Hashtable attributeLabels = new Hashtable ();
\r
76 bool roughLabelCheck;
\r
77 ArrayList strictCheckCache;
\r
80 internal string CurrentStateXml {
\r
81 get { return RdpUtil.DebugRdpPattern (vState, new Hashtable ()); }
\r
84 internal string PreviousStateXml {
\r
85 get { return RdpUtil.DebugRdpPattern (prevState, new Hashtable ()); }
\r
88 #region Validation State support
\r
90 public bool ReportDetails {
\r
91 get { return reportDetails; }
\r
92 set { reportDetails = value; }
\r
95 public bool RoughLabelCheck {
\r
96 get { return roughLabelCheck; }
\r
97 set { roughLabelCheck = value; }
\r
100 // It is used to disclose its validation feature to public
\r
101 class ValidationState
\r
105 internal ValidationState (RdpPattern startState)
\r
107 this.state = startState;
\r
110 public RdpPattern Pattern {
\r
111 get { return state; }
\r
114 public ValidationState AfterOpenStartTag (
\r
115 string localName, string ns)
\r
117 RdpPattern p = state.StartTagOpenDeriv (
\r
119 return p is RdpNotAllowed ?
\r
120 null : new ValidationState (p);
\r
123 public bool OpenStartTag (string localName, string ns)
\r
125 RdpPattern p = state.StartTagOpenDeriv (
\r
127 if (p is RdpNotAllowed)
\r
133 public ValidationState AfterCloseStartTag ()
\r
135 RdpPattern p = state.StartTagCloseDeriv ();
\r
136 return p is RdpNotAllowed ?
\r
137 null : new ValidationState (p);
\r
140 public bool CloseStartTag ()
\r
142 RdpPattern p = state.StartTagCloseDeriv ();
\r
143 if (p is RdpNotAllowed)
\r
149 public ValidationState AfterEndTag ()
\r
151 RdpPattern p = state.EndTagDeriv ();
\r
152 if (p is RdpNotAllowed)
\r
154 return new ValidationState (p);
\r
157 public bool EndTag ()
\r
159 RdpPattern p = state.EndTagDeriv ();
\r
160 if (p is RdpNotAllowed)
\r
166 public ValidationState AfterAttribute (
\r
167 string localName, string ns, XmlReader reader)
\r
169 RdpPattern p = state.AttDeriv (
\r
170 localName, ns, null, reader);
\r
171 if (p is RdpNotAllowed)
\r
173 return new ValidationState (p);
\r
176 public bool Attribute (
\r
177 string localName, string ns, XmlReader reader)
\r
179 RdpPattern p = state.AttDeriv (
\r
180 localName, ns, null, reader);
\r
181 if (p is RdpNotAllowed)
\r
188 public object GetCurrentState ()
\r
191 return new ValidationState (vState);
\r
194 private ValidationState ToState (object stateObject)
\r
196 if (stateObject == null)
\r
197 throw new ArgumentNullException ("stateObject");
\r
198 ValidationState state = stateObject as ValidationState;
\r
200 throw new ArgumentException ("Argument stateObject is not of expected type.");
\r
204 public object AfterOpenStartTag (object stateObject,
\r
205 string localName, string ns)
\r
207 ValidationState state = ToState (stateObject);
\r
208 return state.AfterOpenStartTag (localName, ns);
\r
211 public bool OpenStartTag (object stateObject,
\r
212 string localName, string ns)
\r
214 ValidationState state = ToState (stateObject);
\r
215 return state.OpenStartTag (localName, ns);
\r
218 public object AfterAttribute (object stateObject,
\r
219 string localName, string ns)
\r
221 ValidationState state = ToState (stateObject);
\r
222 return state.AfterAttribute (localName, ns, this);
\r
225 public bool Attribute (object stateObject,
\r
226 string localName, string ns)
\r
228 ValidationState state = ToState (stateObject);
\r
229 return state.Attribute (localName, ns, this);
\r
232 public object AfterCloseStartTag (object stateObject)
\r
234 ValidationState state = ToState (stateObject);
\r
235 return state.AfterCloseStartTag ();
\r
238 public bool CloseStartTag (object stateObject)
\r
240 ValidationState state = ToState (stateObject);
\r
241 return state.CloseStartTag ();
\r
244 public object AfterEndTag (object stateObject)
\r
246 ValidationState state = ToState (stateObject);
\r
247 return state.AfterEndTag ();
\r
250 public bool EndTag (object stateObject)
\r
252 ValidationState state = ToState (stateObject);
\r
253 return state.EndTag ();
\r
256 public ICollection GetElementLabels (object stateObject)
\r
258 ValidationState state = ToState (stateObject);
\r
259 RdpPattern p = state.Pattern;
\r
260 Hashtable elements = new Hashtable ();
\r
261 Hashtable attributes = new Hashtable ();
\r
262 p.GetLabels (elements, attributes);
\r
264 if (roughLabelCheck)
\r
265 return elements.Values;
\r
267 // Strict check that tries actual validation that will
\r
268 // cover rejection by notAllowed.
\r
269 if (strictCheckCache == null)
\r
270 strictCheckCache = new ArrayList ();
\r
272 strictCheckCache.Clear ();
\r
273 foreach (XmlQualifiedName qname in elements.Values)
\r
274 if (p.StartTagOpenDeriv (qname.Name, qname.Namespace) is RdpNotAllowed)
\r
275 strictCheckCache.Add (qname);
\r
276 foreach (XmlQualifiedName qname in strictCheckCache)
\r
277 elements.Remove (qname);
\r
278 strictCheckCache.Clear ();
\r
280 return elements.Values;
\r
283 public ICollection GetAttributeLabels (object stateObject)
\r
285 ValidationState state = ToState (stateObject);
\r
286 RdpPattern p = state.Pattern;
\r
287 Hashtable elements = new Hashtable ();
\r
288 Hashtable attributes = new Hashtable ();
\r
289 p.GetLabels (elements, attributes);
\r
291 if (roughLabelCheck)
\r
292 return attributes.Values;
\r
294 // Strict check that tries actual validation that will
\r
295 // cover rejection by notAllowed.
\r
296 if (strictCheckCache == null)
\r
297 strictCheckCache = new ArrayList ();
\r
299 strictCheckCache.Clear ();
\r
300 foreach (XmlQualifiedName qname in attributes.Values)
\r
301 if (p.AttDeriv (qname.Name, qname.Namespace,null, this) is RdpNotAllowed)
\r
302 strictCheckCache.Add (qname);
\r
303 foreach (XmlQualifiedName qname in strictCheckCache)
\r
304 attributes.Remove (qname);
\r
305 strictCheckCache.Clear ();
\r
307 return attributes.Values;
\r
310 public bool Emptiable (object stateObject)
\r
312 ValidationState state = ToState (stateObject);
\r
313 RdpPattern p = state.Pattern;
\r
314 return !(p.EndTagDeriv () is RdpNotAllowed);
\r
318 private RelaxngException createValidationError (string message)
\r
320 IXmlLineInfo li = reader as IXmlLineInfo;
\r
321 string lineInfo = reader.BaseURI;
\r
323 lineInfo += String.Format (" line {0}, column {1}",
\r
324 li.LineNumber, li.LinePosition);
\r
325 return new RelaxngException (message + lineInfo, prevState);
\r
328 private void PrepareState ()
\r
330 if (!pattern.IsCompiled) {
\r
331 pattern.Compile ();
\r
333 if (vState == null)
\r
334 vState = pattern.StartPattern;
\r
337 private string BuildLabels (bool elements)
\r
339 StringBuilder sb = new StringBuilder ();
\r
340 ValidationState s = new ValidationState (prevState);
\r
341 ICollection col = elements ?
\r
342 GetElementLabels (s) : GetAttributeLabels (s);
\r
343 foreach (XmlQualifiedName qname in col) {
\r
344 sb.Append (qname.ToString ());
\r
347 return sb.ToString ();
\r
350 public override bool Read ()
\r
354 labelsComputed = false;
\r
355 elementLabels.Clear ();
\r
356 attributeLabels.Clear ();
\r
358 bool ret = reader.Read ();
\r
360 switch (reader.NodeType) {
\r
361 case XmlNodeType.Element:
\r
362 // StartTagOpenDeriv
\r
363 prevState = vState;
\r
364 vState = vState.StartTagOpenDeriv (
\r
365 reader.LocalName, reader.NamespaceURI);
\r
366 if (vState.PatternType == RelaxngPatternType.NotAllowed) {
\r
367 string labels = String.Empty;
\r
369 labels = "Allowed elements are: " + BuildLabels (true);
\r
370 throw createValidationError (String.Format ("Invalid start tag found. LocalName = {0}, NS = {1}. {2}", reader.LocalName, reader.NamespaceURI, labels));
\r
373 // AttsDeriv equals to for each AttDeriv
\r
374 string elementNS = reader.NamespaceURI;
\r
375 if (reader.MoveToFirstAttribute ()) {
\r
377 if (reader.Name.IndexOf ("xmlns:") == 0 || reader.Name == "xmlns")
\r
380 prevState = vState;
\r
381 string attrNS = reader.NamespaceURI;
\r
382 vState = vState.AttDeriv (reader.LocalName, attrNS, reader.GetAttribute (reader.Name), this);
\r
383 if (vState.PatternType == RelaxngPatternType.NotAllowed) {
\r
384 string labels = String.Empty;
\r
386 labels = "Allowed attributes are: " + BuildLabels (false);
\r
388 throw createValidationError (String.Format ("Invalid attribute found. LocalName = {0}, NS = {1}. {2}", reader.LocalName, reader.NamespaceURI, labels));
\r
390 } while (reader.MoveToNextAttribute ());
\r
394 // StarTagCloseDeriv
\r
395 prevState = vState;
\r
396 vState = vState.StartTagCloseDeriv ();
\r
397 if (vState.PatternType == RelaxngPatternType.NotAllowed) {
\r
398 string labels = String.Empty;
\r
400 labels = "Expected attributes are: " + BuildLabels (false);
\r
402 throw createValidationError (String.Format ("Invalid start tag closing found. LocalName = {0}, NS = {1}. {2}", reader.LocalName, reader.NamespaceURI, labels));
\r
405 // if it is empty, then redirect to EndElement
\r
406 if (reader.IsEmptyElement)
\r
407 goto case XmlNodeType.EndElement;
\r
409 case XmlNodeType.EndElement:
\r
411 prevState = vState;
\r
412 vState = vState.EndTagDeriv ();
\r
413 if (vState.PatternType == RelaxngPatternType.NotAllowed) {
\r
414 string labels = String.Empty;
\r
416 labels = "Expected elements are: " + BuildLabels (true);
\r
417 throw createValidationError (String.Format ("Invalid end tag found. LocalName = {0}, NS = {1}. {2}", reader.LocalName, reader.NamespaceURI, labels));
\r
420 case XmlNodeType.CDATA:
\r
421 case XmlNodeType.Text:
\r
422 case XmlNodeType.SignificantWhitespace:
\r
423 // Whitespace cannot be skipped because data and
\r
424 // value types are required to validate whitespaces.
\r
425 prevState = vState;
\r
426 vState = vState.TextDeriv (this.Value, reader);
\r
427 if (vState.PatternType == RelaxngPatternType.NotAllowed)
\r
428 throw createValidationError (String.Format ("Invalid text found. Text value = {0} ", reader.Value));
\r